mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
feat: add quest class and session getActiveStages
This commit is contained in:
1 parent
471b781d85
commit
e622011b21
13 files changed
+127
-49
No files matched your search
+1
-2
@@ -49,7 +49,7 @@ module.exports = {
|
|||||||
|
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
'error',
|
'warn',
|
||||||
{
|
{
|
||||||
args: 'after-used',
|
args: 'after-used',
|
||||||
argsIgnorePattern: '^_',
|
argsIgnorePattern: '^_',
|
||||||
@@ -73,7 +73,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
'no-unused-vars': 'off',
|
'no-unused-vars': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': 'error',
|
|
||||||
'no-useless-constructor': 'off',
|
'no-useless-constructor': 'off',
|
||||||
'@typescript-eslint/no-useless-constructor': 'error',
|
'@typescript-eslint/no-useless-constructor': 'error',
|
||||||
'no-empty-function': 'off',
|
'no-empty-function': 'off',
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { IQuest, IStage } from './schema'
|
||||||
|
import { Stage } from './Stage'
|
||||||
|
|
||||||
|
export class Quest {
|
||||||
|
private stages: Stage[]
|
||||||
|
private quest: IQuest
|
||||||
|
|
||||||
|
constructor(quest: IQuest) {
|
||||||
|
this.quest = quest
|
||||||
|
this.stages = quest.stages.map((stage: IStage) => new Stage(stage))
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveStages(completed: string[]) {
|
||||||
|
return this.stages.filter((stage) => stage.active(completed))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
|
import { Quest } from './Quest'
|
||||||
|
import { IQuest } from './schema'
|
||||||
import { ISession } from './types'
|
import { ISession } from './types'
|
||||||
|
|
||||||
export class Session implements ISession {
|
export class Session implements ISession {
|
||||||
private completed: string[] = []
|
private completed: string[] = []
|
||||||
|
private quest: Quest
|
||||||
|
|
||||||
constructor({ completedStages }: ISession) {
|
constructor({ completedStages }: ISession, quest: IQuest) {
|
||||||
this.completed = completedStages
|
this.completed = completedStages
|
||||||
|
this.quest = new Quest(quest)
|
||||||
}
|
}
|
||||||
|
|
||||||
get completedStages() {
|
get completedStages() {
|
||||||
@@ -14,4 +18,8 @@ export class Session implements ISession {
|
|||||||
complete(stageId: string) {
|
complete(stageId: string) {
|
||||||
this.completed.push(stageId)
|
this.completed.push(stageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getActiveStages(): string[] {
|
||||||
|
return this.quest.getActiveStages(this.completed).map((stage) => stage.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IStage, ICommand, IFileExistenceChecker, ISession } from './types'
|
import { IStage, ICommand, IFileExistenceChecker } from './types'
|
||||||
import { Condition } from './condition/Condition'
|
import { Condition } from './condition/Condition'
|
||||||
|
|
||||||
export class Stage {
|
export class Stage {
|
||||||
@@ -21,17 +21,15 @@ export class Stage {
|
|||||||
return this.condition.match(command) && this.condition.check(checker)
|
return this.condition.match(command) && this.condition.check(checker)
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkRequirements(session: ISession): boolean {
|
private checkRequirements(completed: string[]): boolean {
|
||||||
return this.requirements.every((req) =>
|
return this.requirements.every((req) => completed.includes(req))
|
||||||
session.completedStages.includes(req)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private finished(session: ISession): boolean {
|
private finished(completed: string[]): boolean {
|
||||||
return session.completedStages.includes(this.id)
|
return completed.includes(this.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
active(session: ISession): boolean {
|
active(completed: string[]): boolean {
|
||||||
return !this.finished(session) && this.checkRequirements(session)
|
return !this.finished(completed) && this.checkRequirements(completed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
ICommand,
|
ICommand,
|
||||||
ICondition,
|
|
||||||
IFileExistenceChecker,
|
IFileExistenceChecker,
|
||||||
FileExistenceInput,
|
FileExistenceInput,
|
||||||
} from '../types.js'
|
} from '../types.js'
|
||||||
|
import { ICondition } from '../schema/condition.js'
|
||||||
import { PwdMatcher } from './PwdMatcher.js'
|
import { PwdMatcher } from './PwdMatcher.js'
|
||||||
import { CommandMatcher } from './CommandMatcher.js'
|
import { CommandMatcher } from './CommandMatcher.js'
|
||||||
import { ErrorMatcher } from './ErrorMatcher.js'
|
import { ErrorMatcher } from './ErrorMatcher.js'
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { FileType } from '../types.js'
|
||||||
|
|
||||||
export const fileInputSchema = z.object({
|
export const fileInputSchema = z.object({
|
||||||
path: z.string(),
|
path: z.string(),
|
||||||
type: z.enum(['file', 'folder']),
|
type: z.nativeEnum(FileType),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type IFileInput = z.infer<typeof fileInputSchema>
|
export type IFileInput = z.infer<typeof fileInputSchema>
|
||||||
@@ -28,6 +29,6 @@ export type ICondition = z.infer<typeof baseConditionSchema> & {
|
|||||||
|
|
||||||
export const conditionSchema: z.ZodType<ICondition> =
|
export const conditionSchema: z.ZodType<ICondition> =
|
||||||
baseConditionSchema.extend({
|
baseConditionSchema.extend({
|
||||||
or: z.lazy(() => z.array(conditionSchema)).optional(),
|
or: z.lazy(() => z.array(conditionSchema).optional()),
|
||||||
not: z.lazy(() => conditionSchema).optional(),
|
not: z.lazy(() => conditionSchema.optional()),
|
||||||
})
|
})
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
export { conditionSchema, ICondition } from './condition.js'
|
export { conditionSchema, ICondition } from './condition.js'
|
||||||
export { stageSchema, IStage } from './stage.js'
|
export { stageSchema, IStage } from './stage.js'
|
||||||
export {
|
export { requirementsSchema, IRequirements } from './requirements.js'
|
||||||
requirementsSchema,
|
export { responseSchema, IResponse } from './response.js'
|
||||||
IRequirements,
|
export { questSchema, IQuest } from './quest.js'
|
||||||
responseSchema,
|
|
||||||
} from './requirements.js'
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
import { stageSchema } from './stage.js'
|
||||||
|
import { requirementsSchema } from './requirements.js'
|
||||||
|
// import { globalExceptionSchema } from './exception.js'
|
||||||
|
// import { requirementSchema } from './requirement.js'
|
||||||
|
|
||||||
|
export const questSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
image: z.string(),
|
||||||
|
instruction: z.string(),
|
||||||
|
requirements: requirementsSchema,
|
||||||
|
stages: z.array(stageSchema),
|
||||||
|
// exceptions: z.array(globalExceptionSchema).default([]),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type IQuest = z.infer<typeof questSchema>
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
export const requirementsSchema = z.object({
|
export const requirementsSchema = z.array(z.string())
|
||||||
requirements: z.array(z.string()),
|
|
||||||
})
|
|
||||||
|
|
||||||
export type IRequirements = z.infer<typeof requirementsSchema>
|
export type IRequirements = z.infer<typeof requirementsSchema>
|
||||||
|
|
||||||
export const responseSchema = z.object({
|
|
||||||
type: z.enum(['narrative', 'dialogue']),
|
|
||||||
content: z.string(),
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const responseSchema = z.object({
|
||||||
|
type: z.enum(['narrative', 'dialogue']),
|
||||||
|
content: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type IResponse = z.infer<typeof responseSchema>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { conditionSchema, responseSchema } from './index.js'
|
import { conditionSchema } from './condition.js'
|
||||||
|
import { responseSchema } from './response.js'
|
||||||
|
|
||||||
export const stageSchema = z.object({
|
export const stageSchema = z.object({
|
||||||
id: z.string().regex(/^[a-zA-Z0-9_-]+$/),
|
id: z.string().regex(/^[a-zA-Z0-9_-]+$/),
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { expect, describe, it } from '@jest/globals'
|
||||||
|
import { IQuest } from '../src/schema'
|
||||||
|
import { Session } from '../src/Session'
|
||||||
|
|
||||||
|
const quest: IQuest = {
|
||||||
|
id: 'quest1',
|
||||||
|
title: 'Quest 1',
|
||||||
|
image: 'image1',
|
||||||
|
instruction: 'Instruction 1',
|
||||||
|
requirements: [],
|
||||||
|
stages: [
|
||||||
|
{
|
||||||
|
id: 'stage1',
|
||||||
|
requirements: [],
|
||||||
|
condition: {},
|
||||||
|
response: { type: 'narrative', content: 'Narrative 1' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'stage2',
|
||||||
|
requirements: ['stage1'],
|
||||||
|
condition: {},
|
||||||
|
response: { type: 'narrative', content: 'Narrative 2' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'stage3',
|
||||||
|
requirements: ['stage1'],
|
||||||
|
condition: {},
|
||||||
|
response: { type: 'narrative', content: 'Narrative 3' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'stage4',
|
||||||
|
requirements: ['stage2', 'stage3'],
|
||||||
|
condition: {},
|
||||||
|
response: { type: 'narrative', content: 'Narrative 4' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Session', () => {
|
||||||
|
it('should return active stages', () => {
|
||||||
|
const session = new Session(
|
||||||
|
{
|
||||||
|
completedStages: [],
|
||||||
|
},
|
||||||
|
quest
|
||||||
|
)
|
||||||
|
expect(session.getActiveStages()).toEqual(['stage1'])
|
||||||
|
|
||||||
|
session.complete('stage1')
|
||||||
|
expect(session.getActiveStages()).toEqual(['stage2', 'stage3'])
|
||||||
|
|
||||||
|
session.complete('stage2')
|
||||||
|
expect(session.getActiveStages()).toEqual(['stage3'])
|
||||||
|
|
||||||
|
session.complete('stage3')
|
||||||
|
expect(session.getActiveStages()).toEqual(['stage4'])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -2,7 +2,6 @@ import { test, expect } from '@jest/globals'
|
|||||||
import { Stage } from '../src/Stage'
|
import { Stage } from '../src/Stage'
|
||||||
import { FileType } from '../src/types'
|
import { FileType } from '../src/types'
|
||||||
import { MockFileChecker } from './asyncCondition.test'
|
import { MockFileChecker } from './asyncCondition.test'
|
||||||
import { Session } from '../src/Session'
|
|
||||||
|
|
||||||
const checker = new MockFileChecker()
|
const checker = new MockFileChecker()
|
||||||
|
|
||||||
@@ -68,21 +67,3 @@ test('check stage with async condition falsy', async () => {
|
|||||||
})
|
})
|
||||||
expect(await stage.satisfies({}, checker)).toBe(false)
|
expect(await stage.satisfies({}, checker)).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('stage active checks', () => {
|
|
||||||
const session = new Session({
|
|
||||||
completedStages: ['stage1'],
|
|
||||||
})
|
|
||||||
const stage3 = new Stage({
|
|
||||||
id: 'stage3',
|
|
||||||
requirements: ['stage1', 'stage2'],
|
|
||||||
condition: {},
|
|
||||||
})
|
|
||||||
expect(stage3.active(session)).toBe(false)
|
|
||||||
|
|
||||||
session.complete('stage2')
|
|
||||||
expect(stage3.active(session)).toBe(true)
|
|
||||||
|
|
||||||
session.complete('stage3')
|
|
||||||
expect(stage3.active(session)).toBe(false)
|
|
||||||
})
|
|
||||||
Reference in new issue
Block a user