@lernapps/app-templates 0.3.0 → 0.3.1

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.
@@ -822,6 +822,22 @@ function explainerTests(root = ".") {
822
822
  function explainerRuntimeTests(root = ".") {
823
823
  explainerSuite(root, "runtime");
824
824
  }
825
+ /** The contrast ratio (WCAG 2.1) of two computed `rgb(…)` colours. */
826
+ function contrastOf(foreground, background) {
827
+ const luminance = (colour) => {
828
+ const [red, green, blue] = (colour.match(/[\d.]+/g) ?? [
829
+ "0",
830
+ "0",
831
+ "0"
832
+ ]).slice(0, 3).map((value) => {
833
+ const channel = Number(value) / 255;
834
+ return channel <= .03928 ? channel / 12.92 : ((channel + .055) / 1.055) ** 2.4;
835
+ });
836
+ return .2126 * red + .7152 * green + .0722 * blue;
837
+ };
838
+ const [light, dark] = [luminance(foreground), luminance(background)].sort((a, b) => b - a);
839
+ return (light + .05) / (dark + .05);
840
+ }
825
841
  /** 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
842
  const desktopOnly = (page) => test.skip((page.viewportSize()?.width ?? 0) <= 400, "only at desktop width");
827
843
  function explainerSuite(root, part) {
@@ -858,6 +874,21 @@ function explainerSuite(root, part) {
858
874
  await expectNojs(page, item);
859
875
  await expectVideos(page, item);
860
876
  });
877
+ for (const scheme of ["light", "dark"]) test.describe(`${scheme} mode`, () => {
878
+ test.use({ colorScheme: scheme });
879
+ for (const item of (app.pages[lang] ?? []).filter((entry) => entry.figures.length > 0)) test(`${item.url || "./"}: the text of the drawn figures has 4.5:1 against the page`, async ({ page }) => {
880
+ await page.goto(`./${item.url}`);
881
+ const colours = await page.evaluate(() => {
882
+ const background = getComputedStyle(document.body).backgroundColor;
883
+ return [...document.querySelectorAll(".block-drawing text")].map((text) => ({
884
+ text: text.textContent ?? "",
885
+ fill: getComputedStyle(text).fill,
886
+ background
887
+ }));
888
+ });
889
+ for (const { text, fill, background } of colours) expect(contrastOf(fill, background), `"${text}" (${fill} on ${background}) in ${scheme} mode`).toBeGreaterThanOrEqual(4.5);
890
+ });
891
+ });
861
892
  for (const item of app.others[lang] ?? []) test(`${item.url || "./"}: its heading, its links and anchors, its languages`, async ({ page }) => {
862
893
  await page.goto(`./${item.url}`);
863
894
  await expect(page.locator("main h1")).toHaveText(item.title);
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "devDependencies": {
11
11
  "@lernapps/app-templates": "file:..",
12
- "@lernapps/tooling": "0.3.1",
12
+ "@lernapps/tooling": "0.5.2",
13
13
  "vite-plus": "1.4.0"
14
14
  },
15
15
  "overrides": {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "devDependencies": {
11
11
  "@lernapps/app-templates": "file:..",
12
- "@lernapps/tooling": "0.3.1",
12
+ "@lernapps/tooling": "0.5.2",
13
13
  "vite-plus": "1.4.0"
14
14
  },
15
15
  "overrides": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lernapps/app-templates",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
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": {
package/quiz/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "devDependencies": {
11
11
  "@lernapps/app-templates": "file:..",
12
- "@lernapps/tooling": "0.3.1",
12
+ "@lernapps/tooling": "0.5.2",
13
13
  "vite-plus": "1.4.0"
14
14
  },
15
15
  "overrides": {
@@ -852,6 +852,19 @@ export function explainerRuntimeTests(root = "."): void {
852
852
  explainerSuite(root, "runtime");
853
853
  }
854
854
 
855
+ /** The contrast ratio (WCAG 2.1) of two computed `rgb(…)` colours. */
856
+ function contrastOf(foreground: string, background: string): number {
857
+ const luminance = (colour: string) => {
858
+ const [red, green, blue] = (colour.match(/[\d.]+/g) ?? ["0", "0", "0"]).slice(0, 3).map((value) => {
859
+ const channel = Number(value) / 255;
860
+ return channel <= 0.03928 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
861
+ });
862
+ return 0.2126 * red! + 0.7152 * green! + 0.0722 * blue!;
863
+ };
864
+ const [light, dark] = [luminance(foreground), luminance(background)].sort((a, b) => b - a);
865
+ return (light! + 0.05) / (dark! + 0.05);
866
+ }
867
+
855
868
  /** 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. */
856
869
  const desktopOnly = (page: Page) => test.skip((page.viewportSize()?.width ?? 0) <= 400, "only at desktop width");
857
870
 
@@ -905,6 +918,33 @@ function explainerSuite(root: string, part: "content" | "runtime"): void {
905
918
  });
906
919
  }
907
920
 
921
+ for (const scheme of ["light", "dark"] as const) {
922
+ test.describe(`${scheme} mode`, () => {
923
+ test.use({ colorScheme: scheme });
924
+ for (const item of (app.pages[lang] ?? []).filter((entry) => entry.figures.length > 0)) {
925
+ test(`${item.url || "./"}: the text of the drawn figures has 4.5:1 against the page`, async ({
926
+ page,
927
+ }) => {
928
+ await page.goto(`./${item.url}`);
929
+ const colours = await page.evaluate(() => {
930
+ const background = getComputedStyle(document.body).backgroundColor;
931
+ return [...document.querySelectorAll(".block-drawing text")].map((text) => ({
932
+ text: text.textContent ?? "",
933
+ fill: getComputedStyle(text).fill,
934
+ background,
935
+ }));
936
+ });
937
+ for (const { text, fill, background } of colours) {
938
+ expect(
939
+ contrastOf(fill, background),
940
+ `"${text}" (${fill} on ${background}) in ${scheme} mode`,
941
+ ).toBeGreaterThanOrEqual(4.5);
942
+ }
943
+ });
944
+ }
945
+ });
946
+ }
947
+
908
948
  for (const item of app.others[lang] ?? []) {
909
949
  test(`${item.url || "./"}: its heading, its links and anchors, its languages`, async ({ page }) => {
910
950
  await page.goto(`./${item.url}`);
@@ -759,22 +759,6 @@ button.explainer-help:focus-visible {
759
759
  border-bottom: 0;
760
760
  }
761
761
 
762
- .explainer-drawing .fig-on {
763
- fill: var(--page-accent);
764
- }
765
-
766
- .explainer-drawing .fig-off {
767
- fill: var(--page-background);
768
- }
769
-
770
- .explainer-drawing .fig-line {
771
- stroke: var(--page-muted);
772
- }
773
-
774
- .explainer-drawing .fig-text {
775
- fill: var(--page-text);
776
- }
777
-
778
762
  /* The choice of the learner's assistant on the start page, shown once the client module runs */
779
763
  .explainer-tutor-choice[hidden] {
780
764
  display: none;
@@ -30,6 +30,24 @@
30
30
  height: auto;
31
31
  }
32
32
 
33
+ /* The drawings take the page's colours, so they follow the dark mode too (the attributes in figures.ts are the fallback
34
+ for an SVG outside a page) */
35
+ .block-drawing .fig-on {
36
+ fill: var(--page-accent);
37
+ }
38
+
39
+ .block-drawing .fig-off {
40
+ fill: var(--page-background);
41
+ }
42
+
43
+ .block-drawing .fig-line {
44
+ stroke: var(--page-muted);
45
+ }
46
+
47
+ .block-drawing .fig-text {
48
+ fill: var(--page-text);
49
+ }
50
+
33
51
  .block-hundred-grid {
34
52
  width: 16rem;
35
53
  }