@lernapps/app-templates 0.1.1 → 0.2.0
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.
- package/README.md +2 -2
- package/dist/explainer-e2e.mjs +27 -6
- package/dist/quiz-e2e.mjs +48 -24
- package/docs/arc42/09-decisions.arc42.md +24 -0
- package/explainer/docs/arc42/05-explainer-building-blocks.arc42.md +7 -3
- package/explainer/e2e/runtime.e2e.ts +6 -0
- package/explainer/package.json +1 -0
- package/interactive/package.json +1 -0
- package/package.json +37 -2
- package/quiz/docs/arc42/05-quiz-building-blocks.arc42.md +7 -3
- package/quiz/e2e/runtime.e2e.ts +5 -0
- package/quiz/package.json +1 -0
- package/runtime/explainer/e2e.ts +165 -124
- package/runtime/quiz/e2e.ts +172 -135
- package/scaffold/explainer/src/app.yaml +5 -0
- package/scaffold/explainer/src/thema/chapter.yaml +4 -0
- package/scaffold/explainer/src/thema/einstieg.yaml +28 -0
- package/scaffold/interactive/src/app.yaml +8 -0
- package/scaffold/interactive/src/core/index.ts +23 -0
- package/scaffold/interactive/src/messages/de.json +6 -0
- package/scaffold/interactive/src/playthroughs/zaehlen.yaml +12 -0
- package/scaffold/interactive/src/ui/App.vue +27 -0
- package/scaffold/quiz/src/quiz.json +31 -0
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ with which a creator and their AI assistant build a learning app.
|
|
|
7
7
|
## Who it is for
|
|
8
8
|
|
|
9
9
|
- **Creators and their assistants** never open this repo: the generator of
|
|
10
|
-
[lernapps/tooling](https://github.com/lernapps/tooling)
|
|
10
|
+
[lernapps/tooling](https://github.com/lernapps/tooling) writes a template's scaffold into a new app. Start at
|
|
11
11
|
[lernapps.net/apps/eintragen/](https://lernapps.net/apps/eintragen/), or, as an assistant, at
|
|
12
12
|
[lernapps.net/tooling/llms.txt](https://lernapps.net/tooling/llms.txt).
|
|
13
13
|
- **Maintainers of an archetype** change its template and its runtime here.
|
|
@@ -24,7 +24,7 @@ feature: assistants read it before they choose an archetype.
|
|
|
24
24
|
| [`explainer/`](explainer/) | an explainer: chapters of pages with sections, formulas, figures, tables, timelines, definitions with a glossary, quotes, code and YouTube videos behind two clicks, and an exercise on every page (the quiz's question types, and `generated` exercises drawn from a seed and property-tested at build), readable without JavaScript, in one or more languages; an optional self-assessment of the app or a chapter, checked as a whole; Vue components for what no block can express | the content as YAML in `src/`: `app.yaml`, a folder per chapter with `chapter.yaml` and its pages, `data/` and `images/`, and `components/` ([schema](runtime/explainer/explainer.v1.schema.json)) |
|
|
25
25
|
| [`interactive/`](interactive/) | an interactive app whose doing is the content (a game, a trainer): a static start page, readable without JavaScript, and an app page with the app's own Vue components, its routes as `app/#<id>`; the build plays its playthroughs against its core | `src/app.yaml` ([schema](runtime/interactive/interactive.v1.schema.json)), the core `src/core/index.ts` (`start(seed, route)`, `step(state, input)` with a key or an action of its root component, optional `tick(state, dt)`), its components in `src/ui/`, data in `src/data/`, and `src/playthroughs/*.yaml` ([schema](runtime/interactive/playthrough.v1.schema.json)) |
|
|
26
26
|
|
|
27
|
-
- [`runtime/`](runtime/): the runtime of each archetype, published to npm as `@lernapps/app-templates` (with the template folders,
|
|
27
|
+
- [`runtime/`](runtime/): the runtime of each archetype, published to npm as `@lernapps/app-templates` (with the template folders, complete example apps, and the scaffold of each archetype: `lernapps create` of `@lernapps/tooling` copies the paths `lernapps.scaffold.<archetype>` in [`package.json`](package.json) lists and the placeholder in [`scaffold/<archetype>/`](scaffold/), and leaves the example in the installed package; [`scripts/scaffold.ts`](scripts/scaffold.ts) writes the same for the CI job `scaffold`); its exports
|
|
28
28
|
are in [`package.json`](package.json), what each does in the comments of its source. The quiz's questions also work
|
|
29
29
|
one by one, for other archetypes that embed them as exercises: `@lernapps/app-templates/quiz/question` (schema,
|
|
30
30
|
validation, rendering, checker, messages; a question's texts may be maps by language), `startQuestion` of `@lernapps/app-templates/quiz` and the helpers of
|
package/dist/explainer-e2e.mjs
CHANGED
|
@@ -805,13 +805,32 @@ async function expectResultLine(request, copied, here, first, id, n) {
|
|
|
805
805
|
expect(response.status(), `${address} answers`).toBe(200);
|
|
806
806
|
expect(await response.text(), "the tutor page has the exercise").toContain(id);
|
|
807
807
|
}
|
|
808
|
-
/**
|
|
808
|
+
/**
|
|
809
|
+
* The tests of an explainer's content, for the content in `root`/src: what every app runs (e2e/explainer.e2e.ts).
|
|
810
|
+
* Without JavaScript every page as its content says, previous and next, the app's own components and the checks of
|
|
811
|
+
* its exercise components. What the runtime does the same in every app is explainerRuntimeTests.
|
|
812
|
+
*/
|
|
809
813
|
function explainerTests(root = ".") {
|
|
814
|
+
explainerSuite(root, "content");
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* The tests of the explainer's runtime on the content in `root`/src: the exercises of each type, the videos, the tutor
|
|
818
|
+
* ways, the start page, the self-assessments, what is stored. The template folder and the fixtures of
|
|
819
|
+
* lernapps/app-templates run them (e2e/runtime.e2e.ts); an app does not, its scaffold has no such file: they test the
|
|
820
|
+
* runtime, which is the same in every app, not the app's content.
|
|
821
|
+
*/
|
|
822
|
+
function explainerRuntimeTests(root = ".") {
|
|
823
|
+
explainerSuite(root, "runtime");
|
|
824
|
+
}
|
|
825
|
+
/** Only at desktop width, in a test of what does not depend on the width; what a page shows is tested at 360 px too. */
|
|
826
|
+
const desktopOnly = (page) => test.skip((page.viewportSize()?.width ?? 0) <= 400, "only at desktop width");
|
|
827
|
+
function explainerSuite(root, part) {
|
|
810
828
|
const app = outline(root);
|
|
829
|
+
const content = part === "content";
|
|
811
830
|
test.describe("explainer", () => {
|
|
812
831
|
test.describe.configure({ mode: "parallel" });
|
|
813
832
|
test.use({ reducedMotion: "reduce" });
|
|
814
|
-
test.describe("without JavaScript", () => {
|
|
833
|
+
if (content) test.describe("without JavaScript", () => {
|
|
815
834
|
test.use({ javaScriptEnabled: false });
|
|
816
835
|
for (const lang of app.languages) {
|
|
817
836
|
for (const item of app.pages[lang] ?? []) test(`${item.url || "./"}: headings, sections, formulas, figures and anchors as in the content`, async ({ page }) => {
|
|
@@ -848,6 +867,7 @@ function explainerTests(root = ".") {
|
|
|
848
867
|
if (item.kind === "start") await expect(page.locator(".explainer-tutor-about h2")).toHaveText(message(item.lang, "tutorTitle"));
|
|
849
868
|
});
|
|
850
869
|
test(`${lang}: previous and next form one chain over all pages`, async ({ page }) => {
|
|
870
|
+
desktopOnly(page);
|
|
851
871
|
const order = (app.pages[lang] ?? []).map((item) => item.url);
|
|
852
872
|
const first = order[0];
|
|
853
873
|
if (first === void 0) throw new Error(`no page in ${lang}`);
|
|
@@ -881,7 +901,7 @@ function explainerTests(root = ".") {
|
|
|
881
901
|
});
|
|
882
902
|
test.describe("with JavaScript", () => {
|
|
883
903
|
test.skip(({ viewport }) => (viewport?.width ?? 0) > 400, "the exercises run at 360 px");
|
|
884
|
-
for (const item of app.pages["de"] ?? []) {
|
|
904
|
+
if (!content) for (const item of app.pages["de"] ?? []) {
|
|
885
905
|
const types = /* @__PURE__ */ new Map();
|
|
886
906
|
for (const exercise of item.exercises) if (!types.has(exercise.type)) types.set(exercise.type, exercise);
|
|
887
907
|
if (types.size === 0) continue;
|
|
@@ -895,13 +915,13 @@ function explainerTests(root = ".") {
|
|
|
895
915
|
expect(kept.length, "an answer per open task").toBe(types.has("open-task") ? 1 : 0);
|
|
896
916
|
});
|
|
897
917
|
}
|
|
898
|
-
for (const item of app.pages["de"] ?? []) {
|
|
918
|
+
if (!content) for (const item of app.pages["de"] ?? []) {
|
|
899
919
|
if (item.videos.length === 0) continue;
|
|
900
920
|
test(`${item.url}: the videos load nothing before a click, the player after it, directly once chosen`, async ({ page, baseURL }) => {
|
|
901
921
|
await checkVideos(page, item, new URL(baseURL ?? page.url()).origin);
|
|
902
922
|
});
|
|
903
923
|
}
|
|
904
|
-
for (const item of app.pages["de"] ?? []) {
|
|
924
|
+
if (content) for (const item of app.pages["de"] ?? []) {
|
|
905
925
|
if (item.components.length === 0) continue;
|
|
906
926
|
test(`${item.url}: each component hydrates without a console error (${item.components.map((component) => component.name).join(", ")})`, async ({ page }) => {
|
|
907
927
|
await checkComponents(page, item);
|
|
@@ -909,6 +929,7 @@ function explainerTests(root = ".") {
|
|
|
909
929
|
});
|
|
910
930
|
}
|
|
911
931
|
});
|
|
932
|
+
if (content) return;
|
|
912
933
|
for (const item of (app.pages["de"] ?? []).filter((candidate) => candidate.exercises.some((exercise) => exercise.kind === "generated"))) test(`${item.url}: the address holds the task of each exercise, and the tutor button gives them to the prompt`, async ({ page }) => {
|
|
913
934
|
await page.addInitScript(() => {
|
|
914
935
|
const copied = [];
|
|
@@ -1308,4 +1329,4 @@ function explainerTests(root = ".") {
|
|
|
1308
1329
|
});
|
|
1309
1330
|
});
|
|
1310
1331
|
}
|
|
1311
|
-
export { explainerTests };
|
|
1332
|
+
export { explainerRuntimeTests, explainerTests };
|
package/dist/quiz-e2e.mjs
CHANGED
|
@@ -8,14 +8,53 @@ import { readFileSync } from "node:fs";
|
|
|
8
8
|
const de = QUIZ_MESSAGES.de;
|
|
9
9
|
/** The quiz's own texts in `lang`. */
|
|
10
10
|
const messagesOf = messagesIn;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* The tests of a quiz's content, for the question bank in `bank` (relative to the app's root): what every app runs
|
|
13
|
+
* (e2e/quiz.e2e.ts). Each question's link opens it with what it shows; without JavaScript every question and its
|
|
14
|
+
* solution can be read; each other language has its page. What the runtime does the same in every app is
|
|
15
|
+
* quizRuntimeTests.
|
|
16
|
+
*/
|
|
12
17
|
function quizTests(bank = "src/quiz.json") {
|
|
18
|
+
quizSuite(bank, "content");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The tests of the quiz's runtime on the question bank in `bank`: the right answer counts and a wrong one does not, with
|
|
22
|
+
* the feedback in view; the seed and the order; a run through every question to the full score; how many questions
|
|
23
|
+
* the learner chooses; what is stored. The template folder and the fixtures of lernapps/app-templates run them
|
|
24
|
+
* (e2e/runtime.e2e.ts); an app does not, its scaffold has no such file: they test the runtime, which is the same in
|
|
25
|
+
* every app, and their time grows with the bank (lernapps/app-templates#125).
|
|
26
|
+
*/
|
|
27
|
+
function quizRuntimeTests(bank = "src/quiz.json") {
|
|
28
|
+
quizSuite(bank, "runtime");
|
|
29
|
+
}
|
|
30
|
+
function quizSuite(bank, part) {
|
|
31
|
+
const content = part === "content";
|
|
13
32
|
const all = JSON.parse(readFileSync(bank, "utf8"));
|
|
14
33
|
const languages = quizLanguages(all);
|
|
15
34
|
const quiz = quizIn(all, "de");
|
|
16
35
|
test.describe("quiz", () => {
|
|
17
36
|
test.use({ reducedMotion: "reduce" });
|
|
18
|
-
|
|
37
|
+
if (content) {
|
|
38
|
+
for (const item of quiz.questions) test(`${item.id} (${item.type}): its link opens it, with what it shows`, async ({ page }) => {
|
|
39
|
+
await page.goto(`./#${questionId(item)}`);
|
|
40
|
+
await expect(question(page, item)).toBeVisible();
|
|
41
|
+
await expect(page.locator(".question:visible")).toHaveCount(1);
|
|
42
|
+
await expect(question(page, item).locator("legend")).toHaveText(item.text);
|
|
43
|
+
if (item.material !== void 0) await expectMaterial(page, item);
|
|
44
|
+
});
|
|
45
|
+
test.describe("without JavaScript", () => {
|
|
46
|
+
test.use({ javaScriptEnabled: false });
|
|
47
|
+
test("every question and its solution can be read", async ({ page }) => {
|
|
48
|
+
await page.goto("./");
|
|
49
|
+
for (const item of quiz.questions) {
|
|
50
|
+
await expect(question(page, item).locator("legend")).toHaveText(item.text);
|
|
51
|
+
await expectMaterial(page, item, { loaded: false });
|
|
52
|
+
await expectBackground(page.locator(`#loesung-${item.id}`), item);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (!content) for (const item of quiz.questions) {
|
|
19
58
|
test(`${item.id} (${item.type}): its link opens it and the right answer counts`, async ({ page }) => {
|
|
20
59
|
await page.goto(`./#${questionId(item)}`);
|
|
21
60
|
await expect(question(page, item)).toBeVisible();
|
|
@@ -34,10 +73,6 @@ function quizTests(bank = "src/quiz.json") {
|
|
|
34
73
|
await expect(reveal.locator(".question-badge-right").first()).toHaveText(de.rightOption);
|
|
35
74
|
} else await expect(reveal).toContainText(de.rightAnswer);
|
|
36
75
|
});
|
|
37
|
-
if (item.material !== void 0) test(`${item.id} (${item.type}): what it shows is there, and its images load`, async ({ page }) => {
|
|
38
|
-
await page.goto(`./#${questionId(item)}`);
|
|
39
|
-
await expectMaterial(page, item);
|
|
40
|
-
});
|
|
41
76
|
test(`${item.id} (${item.type}): a wrong answer does not count`, async ({ page }) => {
|
|
42
77
|
await page.goto(`./#${questionId(item)}`);
|
|
43
78
|
await answerQuestion(page, item, wrongAnswer(item));
|
|
@@ -47,7 +82,7 @@ function quizTests(bank = "src/quiz.json") {
|
|
|
47
82
|
if (item.type === "single-choice" || item.type === "multiple-choice") await expect(question(page, item).locator(".question-badge-chosen").first()).toHaveText(de.yourChoice);
|
|
48
83
|
});
|
|
49
84
|
}
|
|
50
|
-
test("the seed in the address gives the order; without one, the address gets one", async ({ page }) => {
|
|
85
|
+
if (!content) test("the seed in the address gives the order; without one, the address gets one", async ({ page }) => {
|
|
51
86
|
const everything = `&n=${quiz.questions.length}`;
|
|
52
87
|
const order = async (address) => {
|
|
53
88
|
await page.goto(address);
|
|
@@ -69,7 +104,7 @@ function quizTests(bank = "src/quiz.json") {
|
|
|
69
104
|
await page.goto("./");
|
|
70
105
|
await expect.poll(() => new URL(page.url()).searchParams.get("seed")).toMatch(/^\d+$/);
|
|
71
106
|
});
|
|
72
|
-
test("all right answers give the full score, then the solutions show", async ({ page }) => {
|
|
107
|
+
if (!content) test("all right answers give the full score, then the solutions show", async ({ page }) => {
|
|
73
108
|
await page.goto("./");
|
|
74
109
|
if (quiz.questions.length > 1) await page.locator(".quiz-start button").click();
|
|
75
110
|
expect(await page.evaluate(() => Object.keys(localStorage))).toEqual([]);
|
|
@@ -89,7 +124,7 @@ function quizTests(bank = "src/quiz.json") {
|
|
|
89
124
|
expect(stored[0]).toMatch(/^lernapps:.+:last-result$/);
|
|
90
125
|
} else expect(stored).toEqual([]);
|
|
91
126
|
});
|
|
92
|
-
if (quiz.questions.length > 1) test("the learner chooses how many questions to answer; the address keeps the choice", async ({ page }) => {
|
|
127
|
+
if (!content && quiz.questions.length > 1) test("the learner chooses how many questions to answer; the address keeps the choice", async ({ page }) => {
|
|
93
128
|
await page.goto("./?seed=11");
|
|
94
129
|
await expect(page.locator(".quiz-start")).toBeVisible();
|
|
95
130
|
await expect(page.locator(".question:visible")).toHaveCount(0);
|
|
@@ -113,23 +148,12 @@ function quizTests(bank = "src/quiz.json") {
|
|
|
113
148
|
await expect(page.locator(".question")).toHaveCount(1);
|
|
114
149
|
await expect(page.locator(".question")).toHaveAttribute("data-id", item.id);
|
|
115
150
|
});
|
|
116
|
-
test.describe("without JavaScript", () => {
|
|
117
|
-
test.use({ javaScriptEnabled: false });
|
|
118
|
-
test("every question and its solution can be read", async ({ page }) => {
|
|
119
|
-
await page.goto("./");
|
|
120
|
-
for (const item of quiz.questions) {
|
|
121
|
-
await expect(question(page, item).locator("legend")).toHaveText(item.text);
|
|
122
|
-
await expectMaterial(page, item, { loaded: false });
|
|
123
|
-
await expectBackground(page.locator(`#loesung-${item.id}`), item);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
151
|
for (const lang of languages.slice(1)) {
|
|
128
152
|
const local = quizIn(all, lang);
|
|
129
153
|
const words = messagesOf(lang);
|
|
130
154
|
const address = `./${lang}/`;
|
|
131
155
|
test.describe(`in ${lang}`, () => {
|
|
132
|
-
test.describe("without JavaScript", () => {
|
|
156
|
+
if (content) test.describe("without JavaScript", () => {
|
|
133
157
|
test.use({ javaScriptEnabled: false });
|
|
134
158
|
test(`the page is in ${lang}, every question and its solution can be read, the switch leads to every language`, async ({ page }) => {
|
|
135
159
|
await page.goto(address);
|
|
@@ -151,14 +175,14 @@ function quizTests(bank = "src/quiz.json") {
|
|
|
151
175
|
}
|
|
152
176
|
});
|
|
153
177
|
});
|
|
154
|
-
test(`what a question shows, in ${lang}`, async ({ page }) => {
|
|
178
|
+
if (content) test(`what a question shows, in ${lang}`, async ({ page }) => {
|
|
155
179
|
const item = local.questions.find((candidate) => candidate.material !== void 0);
|
|
156
180
|
test.skip(item === void 0, "no question shows content");
|
|
157
181
|
if (item === void 0) return;
|
|
158
182
|
await page.goto(`${address}#${questionId(item)}`);
|
|
159
183
|
await expectMaterial(page, item);
|
|
160
184
|
});
|
|
161
|
-
test(`one question, answered in ${lang}`, async ({ page }) => {
|
|
185
|
+
if (!content) test(`one question, answered in ${lang}`, async ({ page }) => {
|
|
162
186
|
const item = local.questions[0];
|
|
163
187
|
if (item === void 0) throw new Error("the quiz has no questions");
|
|
164
188
|
await page.goto(`${address}#${questionId(item)}`);
|
|
@@ -171,4 +195,4 @@ function quizTests(bank = "src/quiz.json") {
|
|
|
171
195
|
}
|
|
172
196
|
});
|
|
173
197
|
}
|
|
174
|
-
export { answerQuestion, expectBackground, expectFeedbackInView, expectMaterial, quizTests };
|
|
198
|
+
export { answerQuestion, expectBackground, expectFeedbackInView, expectMaterial, quizRuntimeTests, quizTests };
|
|
@@ -228,6 +228,30 @@ addresses: qg-understandable
|
|
|
228
228
|
:::
|
|
229
229
|
```
|
|
230
230
|
|
|
231
|
+
## A New App Gets the Scaffold, Not the Example
|
|
232
|
+
|
|
233
|
+
`lernapps create` copies only the scaffold of the archetype into a new app: the paths that `lernapps.scaffold.<archetype>`
|
|
234
|
+
in this package's `package.json` lists (the entry, the configuration, the e2e entry) and a placeholder as start content
|
|
235
|
+
(`scaffold/<archetype>/`: one question, one page with one exercise, or a counter as the core). The template folder
|
|
236
|
+
stays a complete example app, which this repo checks; an app does not copy it, the creator's assistant looks it up in
|
|
237
|
+
`node_modules/@lernapps/app-templates/<archetype>/`. A copied example shaped the new app too much: its structure,
|
|
238
|
+
its topic and its tone stayed long after the plan said otherwise. The list is data in this package, which alone knows
|
|
239
|
+
what is configuration and what is example; the tooling only reads it. The CI job `scaffold` checks the scaffold as an
|
|
240
|
+
app, so that a new app passes `lernapps check` from its first commit. `lernapps update` never writes the scaffold
|
|
241
|
+
again: it moves libraries, not content. Rejected: a function `scaffold()` exported by the package, which would need the
|
|
242
|
+
built package even in a clone given to `create --templates`; start content inside the template folder, which the
|
|
243
|
+
template's own checks would read as part of the example.
|
|
244
|
+
|
|
245
|
+
```arc42
|
|
246
|
+
:::decision
|
|
247
|
+
id: dec-scaffold
|
|
248
|
+
title: A new app gets the scaffold and a placeholder, not the example app
|
|
249
|
+
status: accepted
|
|
250
|
+
date: 2026-11-02
|
|
251
|
+
addresses: qg-understandable
|
|
252
|
+
:::
|
|
253
|
+
```
|
|
254
|
+
|
|
231
255
|
## Tutor Variant
|
|
232
256
|
|
|
233
257
|
The optional tutor is a handoff to the learner's own assistant: after a click, a prompt (copied or passed to a chosen
|
|
@@ -488,8 +488,12 @@ path: runtime/explainer/assessment.ts
|
|
|
488
488
|
|
|
489
489
|
### End-to-end tests
|
|
490
490
|
|
|
491
|
-
Built. `explainerTests()` derives from the content what every page must
|
|
492
|
-
|
|
491
|
+
Built, in two parts. `explainerTests()` checks the app's content: it derives from the content what every page must
|
|
492
|
+
show and checks it without JavaScript in every language, previous and next, and with JavaScript the app's own
|
|
493
|
+
components and the checks of its exercise components. `explainerRuntimeTests()` checks the runtime, the same in every
|
|
494
|
+
app: one exercise per type and page, the videos, the tutor ways, the start page, the self-assessments, what is stored.
|
|
495
|
+
Only this repo runs the second part, on the template and its fixtures (`explainer/e2e/runtime.e2e.ts`, not in the
|
|
496
|
+
scaffold).
|
|
493
497
|
|
|
494
498
|
```arc42
|
|
495
499
|
:::building-block
|
|
@@ -504,7 +508,7 @@ requires: if-archetype-content, if-runtime-texts, if-questions
|
|
|
504
508
|
|
|
505
509
|
#### Tests interface
|
|
506
510
|
|
|
507
|
-
The app's `e2e/explainer.e2e.ts
|
|
511
|
+
The app's `e2e/explainer.e2e.ts`, part of the scaffold, only calls `explainerTests()`.
|
|
508
512
|
|
|
509
513
|
```arc42
|
|
510
514
|
:::interface
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { explainerRuntimeTests } from "@lernapps/app-templates/explainer/e2e";
|
|
2
|
+
|
|
3
|
+
// The tests of the explainer runtime, which this repo runs on the template and its fixtures. Not part of the scaffold
|
|
4
|
+
// (lernapps.scaffold in the package.json of @lernapps/app-templates): a new app runs only explainerTests(), on its
|
|
5
|
+
// content.
|
|
6
|
+
explainerRuntimeTests();
|
package/explainer/package.json
CHANGED
package/interactive/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lernapps/app-templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The templates of the archetypes of lernapps.net apps, and the runtime of each archetype that apps depend on (the quiz: engine, build step, styles, end-to-end tests, the schema of the question bank, the questions it shares with the explainer are in ./questions; the explainer: build step, client module with its exercises, the islands of its Vue components, styles, end-to-end tests, the schema of its content; the interactive archetype: build step, the start page and the app page around the app's own Vue components and core, its playthroughs, styles, end-to-end tests, the schemas of its start page and its playthroughs)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"explainer",
|
|
15
15
|
"interactive",
|
|
16
16
|
"quiz",
|
|
17
|
+
"scaffold",
|
|
17
18
|
"!**/package-lock.json",
|
|
18
19
|
"!**/node_modules"
|
|
19
20
|
],
|
|
@@ -142,6 +143,40 @@
|
|
|
142
143
|
"explainer",
|
|
143
144
|
"interactive",
|
|
144
145
|
"quiz"
|
|
145
|
-
]
|
|
146
|
+
],
|
|
147
|
+
"scaffold": {
|
|
148
|
+
"explainer": [
|
|
149
|
+
".gitignore",
|
|
150
|
+
"index.html",
|
|
151
|
+
"package.json",
|
|
152
|
+
"tsconfig.json",
|
|
153
|
+
"vite.config.ts",
|
|
154
|
+
"playwright.config.ts",
|
|
155
|
+
"src/main.ts",
|
|
156
|
+
"e2e/explainer.e2e.ts"
|
|
157
|
+
],
|
|
158
|
+
"interactive": [
|
|
159
|
+
".arc42ignore",
|
|
160
|
+
".gitignore",
|
|
161
|
+
"index.html",
|
|
162
|
+
"package.json",
|
|
163
|
+
"tsconfig.json",
|
|
164
|
+
"vite.config.ts",
|
|
165
|
+
"playwright.config.ts",
|
|
166
|
+
"src/main.ts",
|
|
167
|
+
"src/env.d.ts",
|
|
168
|
+
"e2e/interactive.e2e.ts"
|
|
169
|
+
],
|
|
170
|
+
"quiz": [
|
|
171
|
+
".gitignore",
|
|
172
|
+
"index.html",
|
|
173
|
+
"package.json",
|
|
174
|
+
"tsconfig.json",
|
|
175
|
+
"vite.config.ts",
|
|
176
|
+
"playwright.config.ts",
|
|
177
|
+
"src/main.ts",
|
|
178
|
+
"e2e/quiz.e2e.ts"
|
|
179
|
+
]
|
|
180
|
+
}
|
|
146
181
|
}
|
|
147
182
|
}
|
|
@@ -196,8 +196,12 @@ path: runtime/quiz/engine.ts
|
|
|
196
196
|
|
|
197
197
|
### End-to-end tests
|
|
198
198
|
|
|
199
|
-
Built. `quizTests()` checks
|
|
200
|
-
|
|
199
|
+
Built, in two parts. `quizTests()` checks the app's content: each question's deep link with what it shows, the page
|
|
200
|
+
without JavaScript with every question and its solution, and each further language. `quizRuntimeTests()` checks the
|
|
201
|
+
runtime, the same in every app: right and wrong answer with the feedback in view and the focus, the seed and the order,
|
|
202
|
+
the run to the full score, the number of questions, what is stored. Only this repo runs the second part, on the
|
|
203
|
+
template and its fixtures (`quiz/e2e/runtime.e2e.ts`, not in the scaffold): its time grows with the bank, and it tests
|
|
204
|
+
nothing an app writes.
|
|
201
205
|
|
|
202
206
|
```arc42
|
|
203
207
|
:::building-block
|
|
@@ -212,7 +216,7 @@ requires: if-archetype-content, if-runtime-texts
|
|
|
212
216
|
|
|
213
217
|
#### Tests interface
|
|
214
218
|
|
|
215
|
-
The app's `e2e/quiz.e2e.ts
|
|
219
|
+
The app's `e2e/quiz.e2e.ts`, part of the scaffold, only calls `quizTests()`.
|
|
216
220
|
|
|
217
221
|
```arc42
|
|
218
222
|
:::interface
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { quizRuntimeTests } from "@lernapps/app-templates/quiz/e2e";
|
|
2
|
+
|
|
3
|
+
// The tests of the quiz runtime, which this repo runs on the template and its fixtures. Not part of the scaffold
|
|
4
|
+
// (lernapps.scaffold in the package.json of @lernapps/app-templates): a new app runs only quizTests(), on its content.
|
|
5
|
+
quizRuntimeTests();
|