aboutsummaryrefslogtreecommitdiff
path: root/scripts/test-course.mjs
blob: 7a64dc8a241a5d6784cecf4bb6764d23768840a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import fs from 'fs';
import vm from 'vm';
const SRC = '/var/www/webx11.duckdns.org/spaces/newos4/modules/ext/course.js';
const MANIFEST = { version: 'test-1', updated: '2026-10-18', chapters: [
  { id: '01-orientation', title: 'Orientation', lessons: [{ id: '1.1', title: 'T', objective: 'O', minutes: 5 }] }
], projects: ['p1'] };
const CHAPTER = { id: '01-orientation', title: 'Orientation', lessons: [{ id: '1.1', title: 'T', objective: 'O', minutes: 5,
  blocks: [{ type: 'text', text: 'hello' }, { type: 'cmd', text: 'status' }, { type: 'tip', text: 'tip' }],
  exercise: { instruction: 'touch ok.txt', accept: ['touch ok.txt'], check: { type: 'file', path: '/ok.txt' }, hint: 'touch ok.txt' } }] };
let printed = [];
const terminal = {
  print(t) { printed.push(String(t)); },
  fs: { _f: {}, readFile(p) { return Object.prototype.hasOwnProperty.call(this._f, p) ? this._f[p] : null; },
        writeFile(p, c) { this._f[p] = c; }, mkdirRecursive() {}, fsReadJSON(p) { try { return JSON.parse(this._f[p] || 'null'); } catch (e) { return null; } },
        fsWriteJSON(p, o) { this._f[p] = JSON.stringify(o); } },
  _pushPrintCapture(cb) { this._cap = cb; }, _popPrintCapture() { this._cap && this._cap('output'); },
  ollamaManager: { currentModel: 'test-model' }
};
const store = {};
const sandbox = {
  window: { webx11: { moduleManager: { registerModule() {} } } },
  localStorage: { getItem: k => store[k] ?? null, setItem: (k, v) => { store[k] = String(v); }, removeItem: k => { delete store[k]; } },
  fetch: async (url) => {
    if (url.includes('course.json')) return { ok: true, json: async () => MANIFEST };
    if (url.includes('atlas.json')) return { ok: true, json: async () => ({ commands: { status: { desc: 'Status', module: 'system', tier: 'core-bundle', category: 'system' }, touch: { desc: 'Touch', module: 'fs', tier: 'core-bundle', category: 'filesystem' }, ls: { desc: 'List', module: 'fs', tier: 'core-bundle', category: 'filesystem' } } }) };
    if (url.includes('chapters/')) return { ok: true, json: async () => CHAPTER };
    if (url.includes('event.php')) return { ok: true, json: async () => ({}) };
    if (url.includes('/ai/')) return { ok: true, json: async () => ({ choices: [{ message: { content: 'answer' } }] }) };
    return { ok: false, status: 404, json: async () => ({}) };
  },
  console, document: { body: {} }, setTimeout, clearTimeout, Date, Math, JSON, RegExp
};
sandbox.globalThis = sandbox;
sandbox.webx11 = sandbox.window.webx11;
vm.createContext(sandbox);
vm.runInContext(fs.readFileSync(SRC, 'utf8') + '\n;globalThis.__CourseModule = CourseModule;', sandbox);
const mod = new sandbox.__CourseModule();
mod.terminal = terminal;
let pass = 0, fail = 0;
const check = (n, c, got) => { if (c) { pass++; console.log('PASS', n); } else { fail++; console.log('FAIL', n, got || ''); } };
const run = async () => {
  mod.registerCommands();
  await mod._warm();
  await mod.handle(['list']);
  check('list mentions Orientation', printed.join('\n').includes('Orientation'));
  await mod.handle(['open', '1.1']);
  check('lesson printed', printed.join('\n').includes('Exercise'));
  mod.onCommandStart('touch', ['ok.txt']); terminal.fs.writeFile('/ok.txt', 'x'); mod.onCommandExecuted('touch', ['ok.txt']);
  check('file exercise completes', !!mod._progress().completed['1.1'], JSON.stringify(mod._progress()));
  check('xp awarded', mod._progress().xp >= 10);
  check('match exact', mod._match('ls', 'ls') && !mod._match('ls', 'ls -la'));
  check('match wildcard', mod._match('touch *', 'touch a.txt'));
  await mod.handle(['ask', 'what is status?']);
  check('tutor answered', printed.join('\n').includes('answer'));
  check('skill installed', !!terminal.fs._f['/skills/course-01-orientation.md']);
  console.log(`RESULT ${pass} passed, ${fail} failed`);
  process.exit(fail ? 1 : 0);
};
run().catch(e => { console.error(e); process.exit(1); });