learningfoundry 0.28.0__tar.gz → 0.30.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/CHANGELOG.md +27 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/PKG-INFO +1 -1
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/pyproject.toml +1 -1
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/__init__.py +1 -1
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/package.json +1 -0
- learningfoundry-0.30.0/src/learningfoundry/sveltekit_template/src/lib/stores/curriculum.test.ts +164 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0/src/learningfoundry}/sveltekit_template/src/lib/stores/curriculum.ts +22 -20
- learningfoundry-0.30.0/src/learningfoundry/sveltekit_template/src/routes/+layout.ts +10 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/vite.config.ts +7 -1
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/.gitignore +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/LICENSE +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/README.md +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/docs/project-guide/README.md +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/__main__.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/cli.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/config.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/exceptions.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/generator.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/integrations/__init__.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/integrations/d3foundry_stub.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/integrations/nbfoundry_stub.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/integrations/protocols.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/integrations/quizazz.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/logging_config.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/parser.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/pipeline.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/py.typed +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/resolver.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/schema_v1.py +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/pnpm-lock.yaml +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/app.css +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/app.html +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/ContentBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/ExerciseBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/LessonList.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/LessonView.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/ModuleList.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/Navigation.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/PlaceholderBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/ProgressBar.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/ProgressDashboard.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/QuizBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/TextBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/VideoBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/components/VisualizationBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/db/database.ts +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/db/index.ts +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/db/progress.ts +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/types/index.ts +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/lib/utils/markdown.ts +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/routes/+layout.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/routes/+page.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/routes/[module]/[lesson]/+page.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/static/.gitkeep +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/svelte.config.js +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/tsconfig.json +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/app.css +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/app.html +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/ContentBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/ExerciseBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/LessonList.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/LessonView.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/ModuleList.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/Navigation.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/PlaceholderBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/ProgressBar.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/ProgressDashboard.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/QuizBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/TextBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/VideoBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/components/VisualizationBlock.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/db/database.ts +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/db/index.ts +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/db/progress.ts +0 -0
- {learningfoundry-0.28.0/src/learningfoundry → learningfoundry-0.30.0}/sveltekit_template/src/lib/stores/curriculum.ts +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/types/index.ts +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/utils/markdown.ts +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/routes/+layout.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/routes/+page.svelte +0 -0
- {learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/routes/[module]/[lesson]/+page.svelte +0 -0
|
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.30.0] - 2026-04-27
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`Cannot call fetch eagerly during server-side rendering with relative URL (/curriculum.json)`** — SvelteKit's prerender pass was subscribing to the `curriculum` readable store during SSR of `+layout.svelte`, triggering a relative-URL fetch on the server. The template is a pure CSR SPA (runtime curriculum fetch, IndexedDB, sql.js/WASM) and was never intended to render on the server.
|
|
15
|
+
- `src/learningfoundry/sveltekit_template/src/routes/+layout.ts` — new file exporting `ssr = false` and `prerender = false`. With `adapter-static` + `fallback: 'index.html'` already configured in `svelte.config.js`, the SPA fallback handles every route client-side; prerendering is not needed and was failing on dynamic `[module]/[lesson]` routes anyway.
|
|
16
|
+
|
|
17
|
+
## [0.29.0] - 2026-04-27
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **Lesson content never rendered after clicking "Start module" / Next / Previous.** `navigateTo`, `navigateNext`, and `navigatePrev` in the SvelteKit template only updated the `currentPosition` Svelte store; they never changed the URL. Because lesson content is mounted by the dynamic route `/[module]/[lesson]/+page.svelte`, the route was never visited and `LessonView` (which renders the inlined markdown) never mounted — the left nav title updated, but the content area stayed on the home dashboard.
|
|
22
|
+
- `src/learningfoundry/sveltekit_template/src/lib/stores/curriculum.ts` — `navigateTo` now also calls `goto('/${moduleId}/${lessonId}')`; `navigateNext` and `navigatePrev` refactored to compute the target and delegate to `navigateTo` so URL navigation happens for them too.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Frontend unit-test infrastructure** for the SvelteKit template (the navigation regression went uncaught because the template had no test suite):
|
|
27
|
+
- `sveltekit_template/package.json` — added `jsdom` to devDependencies (`vitest` was already present)
|
|
28
|
+
- `sveltekit_template/vite.config.ts` — added vitest config block (`environment: 'jsdom'`, `include: src/**/*.{test,spec}.{js,ts}`)
|
|
29
|
+
- `sveltekit_template/src/lib/stores/curriculum.test.ts` — 9 cases covering `navigateTo` (URL + store update), `navigateNext` (within module / across modules / final-lesson no-op / null position), `navigatePrev` (within module / across modules / first-lesson no-op / null position); mocks `$app/navigation`'s `goto` via `vi.mock` and stubs global `fetch` to seed the curriculum readable
|
|
30
|
+
- `tests/test_smoke_sveltekit.py::test_pnpm_test_passes` — runs `pnpm test` (vitest) inside the installed template so the smoke run catches future frontend regressions
|
|
31
|
+
|
|
32
|
+
### Verified
|
|
33
|
+
|
|
34
|
+
- `pyve test -m smoke` — 7/7 passed (Python build + vitest)
|
|
35
|
+
- Full Python suite — 195/195 passed; ruff and mypy clean
|
|
36
|
+
|
|
10
37
|
## [0.28.0] - 2026-04-26
|
|
11
38
|
|
|
12
39
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: learningfoundry
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.30.0
|
|
4
4
|
Summary: A curriculum engine that turns a YAML curriculum definition into a deployable SvelteKit learning application.
|
|
5
5
|
Project-URL: Homepage, https://github.com/pointmatic/learningfoundry
|
|
6
6
|
Project-URL: Repository, https://github.com/pointmatic/learningfoundry
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "learningfoundry"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.30.0"
|
|
8
8
|
description = "A curriculum engine that turns a YAML curriculum definition into a deployable SvelteKit learning application."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
learningfoundry-0.30.0/src/learningfoundry/sveltekit_template/src/lib/stores/curriculum.test.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Copyright 2026 Pointmatic
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { get } from 'svelte/store';
|
|
5
|
+
import type { Curriculum } from '$lib/types/index.js';
|
|
6
|
+
|
|
7
|
+
// Mock $app/navigation BEFORE importing the module under test so its
|
|
8
|
+
// top-level `import { goto }` binds to the mock.
|
|
9
|
+
const gotoMock = vi.fn();
|
|
10
|
+
vi.mock('$app/navigation', () => ({
|
|
11
|
+
goto: gotoMock
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
// Two-module fixture covering: same-module advance, cross-module advance,
|
|
15
|
+
// final-lesson edge case, and reverse traversal across module boundaries.
|
|
16
|
+
const FIXTURE: Curriculum = {
|
|
17
|
+
version: 1,
|
|
18
|
+
title: 'Test Curriculum',
|
|
19
|
+
description: 'For navigation tests',
|
|
20
|
+
modules: [
|
|
21
|
+
{
|
|
22
|
+
id: 'mod-01',
|
|
23
|
+
title: 'Module One',
|
|
24
|
+
description: '',
|
|
25
|
+
pre_assessment: null,
|
|
26
|
+
post_assessment: null,
|
|
27
|
+
lessons: [
|
|
28
|
+
{ id: 'lesson-01', title: 'L1', content_blocks: [] },
|
|
29
|
+
{ id: 'lesson-02', title: 'L2', content_blocks: [] }
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'mod-02',
|
|
34
|
+
title: 'Module Two',
|
|
35
|
+
description: '',
|
|
36
|
+
pre_assessment: null,
|
|
37
|
+
post_assessment: null,
|
|
38
|
+
lessons: [
|
|
39
|
+
{ id: 'lesson-01', title: 'M2L1', content_blocks: [] },
|
|
40
|
+
{ id: 'lesson-02', title: 'M2L2', content_blocks: [] }
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
} as unknown as Curriculum;
|
|
45
|
+
|
|
46
|
+
// Stub global fetch so the `curriculum` readable store loads our fixture
|
|
47
|
+
// instead of doing a real network call (jsdom has no /curriculum.json).
|
|
48
|
+
vi.stubGlobal(
|
|
49
|
+
'fetch',
|
|
50
|
+
vi.fn().mockResolvedValue({
|
|
51
|
+
ok: true,
|
|
52
|
+
status: 200,
|
|
53
|
+
statusText: 'OK',
|
|
54
|
+
json: async () => FIXTURE
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Import AFTER vi.mock and vi.stubGlobal so they're applied to the module
|
|
59
|
+
// under test on first load.
|
|
60
|
+
const {
|
|
61
|
+
curriculum,
|
|
62
|
+
currentPosition,
|
|
63
|
+
navigateTo,
|
|
64
|
+
navigateNext,
|
|
65
|
+
navigatePrev
|
|
66
|
+
} = await import('./curriculum.js');
|
|
67
|
+
|
|
68
|
+
// Wait for the readable store's async loader to resolve and emit the
|
|
69
|
+
// fixture. Once `curriculum` has data, the derived `modules` store that
|
|
70
|
+
// `navigateNext`/`navigatePrev` consume internally will have data too.
|
|
71
|
+
beforeAll(async () => {
|
|
72
|
+
await new Promise<void>((resolve) => {
|
|
73
|
+
const unsub = curriculum.subscribe((c) => {
|
|
74
|
+
if (c) {
|
|
75
|
+
unsub();
|
|
76
|
+
resolve();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
gotoMock.mockClear();
|
|
84
|
+
currentPosition.set(null);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('navigateTo', () => {
|
|
88
|
+
it('updates currentPosition and calls goto with /{moduleId}/{lessonId}', () => {
|
|
89
|
+
navigateTo('mod-01', 'lesson-01');
|
|
90
|
+
expect(get(currentPosition)).toEqual({ moduleId: 'mod-01', lessonId: 'lesson-01' });
|
|
91
|
+
expect(gotoMock).toHaveBeenCalledTimes(1);
|
|
92
|
+
expect(gotoMock).toHaveBeenCalledWith('/mod-01/lesson-01');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('navigateNext', () => {
|
|
97
|
+
it('advances to the next lesson within the same module', () => {
|
|
98
|
+
navigateTo('mod-01', 'lesson-01');
|
|
99
|
+
gotoMock.mockClear();
|
|
100
|
+
|
|
101
|
+
navigateNext();
|
|
102
|
+
expect(get(currentPosition)).toEqual({ moduleId: 'mod-01', lessonId: 'lesson-02' });
|
|
103
|
+
expect(gotoMock).toHaveBeenCalledWith('/mod-01/lesson-02');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('crosses module boundaries to the first lesson of the next module', () => {
|
|
107
|
+
navigateTo('mod-01', 'lesson-02');
|
|
108
|
+
gotoMock.mockClear();
|
|
109
|
+
|
|
110
|
+
navigateNext();
|
|
111
|
+
expect(get(currentPosition)).toEqual({ moduleId: 'mod-02', lessonId: 'lesson-01' });
|
|
112
|
+
expect(gotoMock).toHaveBeenCalledWith('/mod-02/lesson-01');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('is a no-op past the final lesson', () => {
|
|
116
|
+
navigateTo('mod-02', 'lesson-02');
|
|
117
|
+
gotoMock.mockClear();
|
|
118
|
+
|
|
119
|
+
navigateNext();
|
|
120
|
+
expect(get(currentPosition)).toEqual({ moduleId: 'mod-02', lessonId: 'lesson-02' });
|
|
121
|
+
expect(gotoMock).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('is a no-op when currentPosition is null', () => {
|
|
125
|
+
navigateNext();
|
|
126
|
+
expect(get(currentPosition)).toBeNull();
|
|
127
|
+
expect(gotoMock).not.toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('navigatePrev', () => {
|
|
132
|
+
it('moves to the previous lesson within the same module', () => {
|
|
133
|
+
navigateTo('mod-01', 'lesson-02');
|
|
134
|
+
gotoMock.mockClear();
|
|
135
|
+
|
|
136
|
+
navigatePrev();
|
|
137
|
+
expect(get(currentPosition)).toEqual({ moduleId: 'mod-01', lessonId: 'lesson-01' });
|
|
138
|
+
expect(gotoMock).toHaveBeenCalledWith('/mod-01/lesson-01');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('crosses module boundaries backward', () => {
|
|
142
|
+
navigateTo('mod-02', 'lesson-01');
|
|
143
|
+
gotoMock.mockClear();
|
|
144
|
+
|
|
145
|
+
navigatePrev();
|
|
146
|
+
expect(get(currentPosition)).toEqual({ moduleId: 'mod-01', lessonId: 'lesson-02' });
|
|
147
|
+
expect(gotoMock).toHaveBeenCalledWith('/mod-01/lesson-02');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('is a no-op before the first lesson', () => {
|
|
151
|
+
navigateTo('mod-01', 'lesson-01');
|
|
152
|
+
gotoMock.mockClear();
|
|
153
|
+
|
|
154
|
+
navigatePrev();
|
|
155
|
+
expect(get(currentPosition)).toEqual({ moduleId: 'mod-01', lessonId: 'lesson-01' });
|
|
156
|
+
expect(gotoMock).not.toHaveBeenCalled();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('is a no-op when currentPosition is null', () => {
|
|
160
|
+
navigatePrev();
|
|
161
|
+
expect(get(currentPosition)).toBeNull();
|
|
162
|
+
expect(gotoMock).not.toHaveBeenCalled();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright 2026 Pointmatic
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { derived, readable, writable } from 'svelte/store';
|
|
4
|
+
import { goto } from '$app/navigation';
|
|
4
5
|
import type { Curriculum, Lesson, Module } from '$lib/types/index.js';
|
|
5
6
|
|
|
6
7
|
// ---------------------------------------------------------------------------
|
|
@@ -87,36 +88,37 @@ export const nextLesson = derived(
|
|
|
87
88
|
|
|
88
89
|
export function navigateTo(moduleId: string, lessonId: string): void {
|
|
89
90
|
currentPosition.set({ moduleId, lessonId });
|
|
91
|
+
void goto(`/${moduleId}/${lessonId}`);
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
export function navigateNext(): void {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
const pos = get(currentPosition);
|
|
96
|
+
if (!pos) return;
|
|
97
|
+
let found = false;
|
|
98
|
+
for (const mod of get(modules) as Module[]) {
|
|
99
|
+
for (const lesson of mod.lessons as Lesson[]) {
|
|
100
|
+
if (found) {
|
|
101
|
+
navigateTo(mod.id, lesson.id);
|
|
102
|
+
return;
|
|
100
103
|
}
|
|
104
|
+
if (mod.id === pos.moduleId && lesson.id === pos.lessonId) found = true;
|
|
101
105
|
}
|
|
102
|
-
|
|
103
|
-
});
|
|
106
|
+
}
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
export function navigatePrev(): void {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
prev = { moduleId: mod.id, lessonId: lesson.id };
|
|
110
|
+
const pos = get(currentPosition);
|
|
111
|
+
if (!pos) return;
|
|
112
|
+
let prev: NavPosition | null = null;
|
|
113
|
+
for (const mod of get(modules) as Module[]) {
|
|
114
|
+
for (const lesson of mod.lessons as Lesson[]) {
|
|
115
|
+
if (mod.id === pos.moduleId && lesson.id === pos.lessonId) {
|
|
116
|
+
if (prev) navigateTo(prev.moduleId, prev.lessonId);
|
|
117
|
+
return;
|
|
116
118
|
}
|
|
119
|
+
prev = { moduleId: mod.id, lessonId: lesson.id };
|
|
117
120
|
}
|
|
118
|
-
|
|
119
|
-
});
|
|
121
|
+
}
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
// get() helper for non-reactive reads inside update callbacks
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright 2026 Pointmatic
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// This app is a client-side SPA: it loads `curriculum.json` at runtime,
|
|
5
|
+
// persists progress in IndexedDB, and uses sql.js (WASM) — none of which
|
|
6
|
+
// can run during server-side rendering. Disable SSR so the prerender
|
|
7
|
+
// pass does not subscribe to the curriculum store and trigger a relative
|
|
8
|
+
// `fetch('/curriculum.json')` on the server.
|
|
9
|
+
export const ssr = false;
|
|
10
|
+
export const prerender = false;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
// Copyright 2026 Pointmatic
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/// <reference types="vitest" />
|
|
3
4
|
import { sveltekit } from '@sveltejs/kit/vite';
|
|
4
5
|
import tailwindcss from '@tailwindcss/vite';
|
|
5
6
|
import { defineConfig } from 'vite';
|
|
6
7
|
|
|
7
8
|
export default defineConfig({
|
|
8
|
-
plugins: [tailwindcss(), sveltekit()]
|
|
9
|
+
plugins: [tailwindcss(), sveltekit()],
|
|
10
|
+
test: {
|
|
11
|
+
environment: 'jsdom',
|
|
12
|
+
include: ['src/**/*.{test,spec}.{js,ts}'],
|
|
13
|
+
globals: false
|
|
14
|
+
}
|
|
9
15
|
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/integrations/__init__.py
RENAMED
|
File without changes
|
{learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/integrations/d3foundry_stub.py
RENAMED
|
File without changes
|
{learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/integrations/nbfoundry_stub.py
RENAMED
|
File without changes
|
{learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/integrations/protocols.py
RENAMED
|
File without changes
|
{learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/integrations/quizazz.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learningfoundry-0.28.0 → learningfoundry-0.30.0}/src/learningfoundry/sveltekit_template/src/app.css
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/lib/utils/markdown.ts
RENAMED
|
File without changes
|
{learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/routes/+layout.svelte
RENAMED
|
File without changes
|
{learningfoundry-0.28.0 → learningfoundry-0.30.0}/sveltekit_template/src/routes/+page.svelte
RENAMED
|
File without changes
|
|
File without changes
|