@robotical/martyblocksjr 4.2.7 → 4.2.9
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/editions/free/src/app.bundle.js +1 -1
- package/editions/free/src/assets/start/marty-blocksjr.svg +1 -0
- package/editions/free/src/css/thumbs.css +163 -0
- package/editions/free/src/inapp/about.html +1 -1
- package/editions/free/src/localizations/bg.json +1 -1
- package/editions/free/src/localizations/ca.json +1 -1
- package/editions/free/src/localizations/cs.json +1 -1
- package/editions/free/src/localizations/cy.json +1 -1
- package/editions/free/src/localizations/da.json +1 -1
- package/editions/free/src/localizations/de.json +1 -1
- package/editions/free/src/localizations/el.json +1 -1
- package/editions/free/src/localizations/en.json +1 -1
- package/editions/free/src/localizations/es.json +1 -1
- package/editions/free/src/localizations/fi.json +1 -1
- package/editions/free/src/localizations/fr.json +1 -1
- package/editions/free/src/localizations/it.json +1 -1
- package/editions/free/src/localizations/ja.json +1 -1
- package/editions/free/src/localizations/ko.json +1 -1
- package/editions/free/src/localizations/nl.json +1 -1
- package/editions/free/src/localizations/no.json +1 -1
- package/editions/free/src/localizations/pl.json +1 -1
- package/editions/free/src/localizations/pt-br.json +1 -1
- package/editions/free/src/localizations/pt.json +1 -1
- package/editions/free/src/localizations/sv.json +1 -1
- package/editions/free/src/localizations/th.json +1 -1
- package/editions/free/src/localizations/tr.json +1 -1
- package/editions/free/src/localizations/uk.json +1 -1
- package/editions/free/src/localizations/zh-cn.json +1 -1
- package/editions/free/src/localizations/zh-tw.json +1 -1
- package/editions/free/src/settings.json +3 -0
- package/package.json +1 -1
- package/tests/CurriculumArtifact.test.js +272 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "Exemples de projets",
|
|
5
5
|
"ABOUT_SCRATCHJR": "À propos de Blocks Jr",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "Progetti di Esempio",
|
|
5
5
|
"ABOUT_SCRATCHJR": "Info su Blocks Jr",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "ぷろじぇくとのれい",
|
|
5
5
|
"ABOUT_SCRATCHJR": "Blocks Jrについて",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "예시 프로젝트",
|
|
5
5
|
"ABOUT_SCRATCHJR": "Blocks Jr 소개",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "Voorbeeldprojecten",
|
|
5
5
|
"ABOUT_SCRATCHJR": "Over Blocks Jr",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "Prosjekt Prøver",
|
|
5
5
|
"ABOUT_SCRATCHJR": "Om Blocks Jr",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "Przykładowe Projekty",
|
|
5
5
|
"ABOUT_SCRATCHJR": "O Blocks Jr",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "Projetos de Exemplo",
|
|
5
5
|
"ABOUT_SCRATCHJR": "Sobre o Blocks Jr",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "Projetos de Exemplo",
|
|
5
5
|
"ABOUT_SCRATCHJR": "Sobre o Blocks Jr",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "Exempelprojekt",
|
|
5
5
|
"ABOUT_SCRATCHJR": "Om Blocks Jr",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "โครงงานตัวอย่าง",
|
|
5
5
|
"ABOUT_SCRATCHJR": "เกี่ยวกับ Blocks Jr",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "Örnek Projeler",
|
|
5
5
|
"ABOUT_SCRATCHJR": "Blocks Jr Hakkında",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "Зразки проєктів",
|
|
5
5
|
"ABOUT_SCRATCHJR": "Про Blocks Jr",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "项目示例",
|
|
5
5
|
"ABOUT_SCRATCHJR": "关于 Blocks Jr",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SAMPLE_PROJECTS": "專案樣本",
|
|
5
5
|
"ABOUT_SCRATCHJR": "關於 Blocks Jr",
|
|
6
6
|
"ABOUT_BLOCKS_JR_EYEBROW": "Made for curious creators",
|
|
7
|
-
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring
|
|
7
|
+
"ABOUT_BLOCKS_JR_TAGLINE": "Create stories. Code characters. Bring learning to life.",
|
|
8
8
|
"ABOUT_BLOCKS_JR_INTRODUCTION_TITLE": "A first step into creative coding",
|
|
9
9
|
"ABOUT_BLOCKS_JR_INTRODUCTION_DESCRIPTION": "Blocks Jr is Robotical's beginner-friendly block coding environment. Young learners snap together colourful blocks to make characters move, tell interactive stories, and control real robots—building confidence through play and experimentation.",
|
|
10
10
|
"ABOUT_BLOCKS_JR_FEATURES_TITLE": "What can you make?",
|
package/package.json
CHANGED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const { importPackageMock } = vi.hoisted(() => ({
|
|
4
|
+
importPackageMock: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock("@/editor/ProjectCloud", () => ({
|
|
8
|
+
default: {
|
|
9
|
+
importPackage: importPackageMock,
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
CURRICULUM_ARTIFACT_KIND,
|
|
15
|
+
CURRICULUM_ARTIFACT_PLATFORM,
|
|
16
|
+
importCurriculumArtifactFromSearch,
|
|
17
|
+
parseCurriculumArtifactRequest,
|
|
18
|
+
sha256Hex,
|
|
19
|
+
validateCurriculumArtifact,
|
|
20
|
+
} from "@/editor/CurriculumArtifact";
|
|
21
|
+
|
|
22
|
+
function validArtifact() {
|
|
23
|
+
return {
|
|
24
|
+
kind: CURRICULUM_ARTIFACT_KIND,
|
|
25
|
+
formatVersion: 1,
|
|
26
|
+
platform: CURRICULUM_ARTIFACT_PLATFORM,
|
|
27
|
+
projectPackage: {
|
|
28
|
+
formatVersion: 1,
|
|
29
|
+
project: {
|
|
30
|
+
id: "source-project-id-must-not-be-reused",
|
|
31
|
+
name: "Walking starter",
|
|
32
|
+
version: "iOSv01",
|
|
33
|
+
gallery: "samples",
|
|
34
|
+
isgift: "1",
|
|
35
|
+
thumbnail: {
|
|
36
|
+
pagecount: 1,
|
|
37
|
+
md5: "thumbnail.png",
|
|
38
|
+
},
|
|
39
|
+
json: {
|
|
40
|
+
pages: ["page 1"],
|
|
41
|
+
currentPage: "page 1",
|
|
42
|
+
"page 1": {
|
|
43
|
+
sprites: ["Marty 1"],
|
|
44
|
+
layers: ["Marty 1"],
|
|
45
|
+
md5: "Farm.svg",
|
|
46
|
+
"Marty 1": {
|
|
47
|
+
id: "Marty 1",
|
|
48
|
+
type: "sprite",
|
|
49
|
+
md5: "marty.svg",
|
|
50
|
+
animationFrames: ["marty.svg", "marty-2.svg"],
|
|
51
|
+
sounds: ["hello.wav"],
|
|
52
|
+
scripts: [],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
assets: {
|
|
58
|
+
"thumbnail.png": "dGh1bWI=",
|
|
59
|
+
"marty.svg": "PHN2Zy8+",
|
|
60
|
+
"marty-2.svg": "PHN2Zy8+",
|
|
61
|
+
"hello.wav": "UklGRg==",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function responseFor(value, url = "https://cdn.sanity.io/files/project/dataset/walking.json") {
|
|
68
|
+
var bytes = new TextEncoder().encode(JSON.stringify(value));
|
|
69
|
+
return {
|
|
70
|
+
ok: true,
|
|
71
|
+
url: url,
|
|
72
|
+
headers: {
|
|
73
|
+
get(name) {
|
|
74
|
+
if (name.toLowerCase() === "content-type") {
|
|
75
|
+
return "application/vnd.robotical.curriculum-code-artifact+json; charset=utf-8";
|
|
76
|
+
}
|
|
77
|
+
if (name.toLowerCase() === "content-length") {
|
|
78
|
+
return String(bytes.byteLength);
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
arrayBuffer: async function () {
|
|
84
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
describe("curriculum artifact import", () => {
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
importPackageMock.mockReset();
|
|
92
|
+
importPackageMock.mockResolvedValue({ projectId: "42" });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("normalizes a valid artifact into a fresh editable project package", () => {
|
|
96
|
+
var result = validateCurriculumArtifact(validArtifact());
|
|
97
|
+
|
|
98
|
+
expect(result.formatVersion).toBe(1);
|
|
99
|
+
expect(result.project).toMatchObject({
|
|
100
|
+
name: "Walking starter",
|
|
101
|
+
version: "iOSv01",
|
|
102
|
+
deleted: "NO",
|
|
103
|
+
isgift: "0",
|
|
104
|
+
});
|
|
105
|
+
expect(result.project.id).toBeUndefined();
|
|
106
|
+
expect(result.project.gallery).toBeUndefined();
|
|
107
|
+
expect(Object.keys(result.assets).sort()).toEqual([
|
|
108
|
+
"hello.wav",
|
|
109
|
+
"marty-2.svg",
|
|
110
|
+
"marty.svg",
|
|
111
|
+
"thumbnail.png",
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("rejects unsupported envelopes and packages", () => {
|
|
116
|
+
var artifact = validArtifact();
|
|
117
|
+
artifact.platform = "martyblocks";
|
|
118
|
+
expect(() => validateCurriculumArtifact(artifact)).toThrow(/platform/);
|
|
119
|
+
|
|
120
|
+
artifact = validArtifact();
|
|
121
|
+
artifact.formatVersion = 2;
|
|
122
|
+
expect(() => validateCurriculumArtifact(artifact)).toThrow(/formatVersion/);
|
|
123
|
+
|
|
124
|
+
artifact = validArtifact();
|
|
125
|
+
artifact.projectPackage.formatVersion = 2;
|
|
126
|
+
expect(() => validateCurriculumArtifact(artifact)).toThrow(/projectPackage formatVersion/);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("rejects remote, traversal and unreferenced project assets", () => {
|
|
130
|
+
var artifact = validArtifact();
|
|
131
|
+
artifact.projectPackage.project.json["page 1"].md5 = "https://evil.example/background.svg";
|
|
132
|
+
expect(() => validateCurriculumArtifact(artifact)).toThrow(/safe relative asset identifier/);
|
|
133
|
+
|
|
134
|
+
artifact = validArtifact();
|
|
135
|
+
artifact.projectPackage.project.json["page 1"]["Marty 1"].md5 = "../marty.svg";
|
|
136
|
+
expect(() => validateCurriculumArtifact(artifact)).toThrow(/safe relative asset identifier/);
|
|
137
|
+
|
|
138
|
+
artifact = validArtifact();
|
|
139
|
+
artifact.projectPackage.assets["unused.svg"] = "PHN2Zy8+";
|
|
140
|
+
expect(() => validateCurriculumArtifact(artifact)).toThrow(/unreferenced asset/);
|
|
141
|
+
|
|
142
|
+
artifact = validArtifact();
|
|
143
|
+
artifact.projectPackage.project.json["page 1"]["Marty 1"].md5 = "sprites/%2e%2e/marty.svg";
|
|
144
|
+
expect(() => validateCurriculumArtifact(artifact)).toThrow(/safe relative asset identifier/);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("rejects project structures that the editor cannot safely recreate", () => {
|
|
148
|
+
var artifact = validArtifact();
|
|
149
|
+
delete artifact.projectPackage.project.json["page 1"].layers;
|
|
150
|
+
expect(() => validateCurriculumArtifact(artifact)).toThrow(/layers/);
|
|
151
|
+
|
|
152
|
+
artifact = validArtifact();
|
|
153
|
+
artifact.projectPackage.project.json["page 1"].layers = [];
|
|
154
|
+
expect(() => validateCurriculumArtifact(artifact)).toThrow(/every sprite/);
|
|
155
|
+
|
|
156
|
+
artifact = validArtifact();
|
|
157
|
+
delete artifact.projectPackage.project.json["page 1"]["Marty 1"].scripts;
|
|
158
|
+
expect(() => validateCurriculumArtifact(artifact)).toThrow(/scripts/);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("accepts only configured HTTPS URLs and loopback development URLs", () => {
|
|
162
|
+
expect(parseCurriculumArtifactRequest(
|
|
163
|
+
"?curriculumArtifactUrl=https%3A%2F%2Fcdn.sanity.io%2Fartifact.json" +
|
|
164
|
+
"&curriculumArtifactSha256=" + "a".repeat(64),
|
|
165
|
+
{
|
|
166
|
+
baseUrl: "https://app.example/blocks-jr",
|
|
167
|
+
allowedOrigins: ["https://cdn.sanity.io"],
|
|
168
|
+
}
|
|
169
|
+
).url).toBe("https://cdn.sanity.io/artifact.json");
|
|
170
|
+
|
|
171
|
+
expect(parseCurriculumArtifactRequest(
|
|
172
|
+
"?curriculumArtifactUrl=http%3A%2F%2Flocalhost%3A3011%2Fartifact.json",
|
|
173
|
+
{ baseUrl: "http://localhost:3011/blocks-jr" }
|
|
174
|
+
).url).toBe("http://localhost:3011/artifact.json");
|
|
175
|
+
|
|
176
|
+
expect(() => parseCurriculumArtifactRequest(
|
|
177
|
+
"?curriculumArtifactUrl=https%3A%2F%2Fevil.example%2Fartifact.json" +
|
|
178
|
+
"&curriculumArtifactSha256=" + "a".repeat(64),
|
|
179
|
+
{ baseUrl: "https://app.example/blocks-jr" }
|
|
180
|
+
)).toThrow(/origin is not allowed/);
|
|
181
|
+
|
|
182
|
+
expect(() => parseCurriculumArtifactRequest(
|
|
183
|
+
"?curriculumArtifactUrl=https%3A%2F%2Fapp.example%2Fartifact.json" +
|
|
184
|
+
"&curriculumArtifactSha256=" + "a".repeat(64),
|
|
185
|
+
{ baseUrl: "https://app.example/blocks-jr" }
|
|
186
|
+
)).toThrow(/origin is not allowed/);
|
|
187
|
+
|
|
188
|
+
expect(() => parseCurriculumArtifactRequest(
|
|
189
|
+
"?curriculumArtifactUrl=%2Fartifact.json&curriculumArtifactSha256=" + "a".repeat(64),
|
|
190
|
+
{ baseUrl: "https://app.example/blocks-jr" }
|
|
191
|
+
)).toThrow(/absolute URL/);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("allows explicitly configured artifact origins", () => {
|
|
195
|
+
var request = parseCurriculumArtifactRequest(
|
|
196
|
+
"?curriculumArtifactUrl=https%3A%2F%2Fcontent.robotical.io%2Fartifact.json" +
|
|
197
|
+
"&curriculumArtifactSha256=" + "a".repeat(64),
|
|
198
|
+
{
|
|
199
|
+
baseUrl: "https://app.example/blocks-jr",
|
|
200
|
+
allowedOrigins: ["https://content.robotical.io"],
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
expect(request.url).toBe("https://content.robotical.io/artifact.json");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("requires payload integrity outside loopback development", () => {
|
|
207
|
+
expect(() => parseCurriculumArtifactRequest(
|
|
208
|
+
"?curriculumArtifactUrl=https%3A%2F%2Fcdn.sanity.io%2Fartifact.json",
|
|
209
|
+
{
|
|
210
|
+
baseUrl: "https://app.example/blocks-jr",
|
|
211
|
+
allowedOrigins: ["https://cdn.sanity.io"],
|
|
212
|
+
}
|
|
213
|
+
)).toThrow(/required outside local development/);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("fetches, verifies and imports a package as a new local project", async () => {
|
|
217
|
+
var response = responseFor(validArtifact());
|
|
218
|
+
var body = await response.arrayBuffer();
|
|
219
|
+
var digest = sha256Hex(body);
|
|
220
|
+
var fetchMock = vi.fn().mockResolvedValue(response);
|
|
221
|
+
var result = await importCurriculumArtifactFromSearch(
|
|
222
|
+
"?curriculumArtifactUrl=https%3A%2F%2Fcdn.sanity.io%2Ffiles%2Fproject%2Fdataset%2Fwalking.json" +
|
|
223
|
+
"&curriculumArtifactSha256=" + digest,
|
|
224
|
+
{
|
|
225
|
+
baseUrl: "https://app.example/blocks-jr",
|
|
226
|
+
allowedOrigins: ["https://cdn.sanity.io"],
|
|
227
|
+
fetchImplementation: fetchMock,
|
|
228
|
+
projectVersion: "iOSv01",
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
expect(result).toEqual({ projectId: "42" });
|
|
233
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
234
|
+
"https://cdn.sanity.io/files/project/dataset/walking.json",
|
|
235
|
+
expect.objectContaining({
|
|
236
|
+
credentials: "omit",
|
|
237
|
+
cache: "no-store",
|
|
238
|
+
referrerPolicy: "no-referrer",
|
|
239
|
+
})
|
|
240
|
+
);
|
|
241
|
+
expect(importPackageMock).toHaveBeenCalledTimes(1);
|
|
242
|
+
expect(importPackageMock.mock.calls[0][0].project.id).toBeUndefined();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("does not write anything when the checksum is wrong", async () => {
|
|
246
|
+
var fetchMock = vi.fn().mockResolvedValue(responseFor(validArtifact()));
|
|
247
|
+
await expect(importCurriculumArtifactFromSearch(
|
|
248
|
+
"?curriculumArtifactUrl=https%3A%2F%2Fcdn.sanity.io%2Ffiles%2Fproject%2Fdataset%2Fwalking.json" +
|
|
249
|
+
"&curriculumArtifactSha256=" + "0".repeat(64),
|
|
250
|
+
{
|
|
251
|
+
baseUrl: "https://app.example/blocks-jr",
|
|
252
|
+
allowedOrigins: ["https://cdn.sanity.io"],
|
|
253
|
+
fetchImplementation: fetchMock,
|
|
254
|
+
}
|
|
255
|
+
)).rejects.toThrow(/checksum/);
|
|
256
|
+
expect(importPackageMock).not.toHaveBeenCalled();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("does not write anything when a redirect crosses an allowed-origin boundary", async () => {
|
|
260
|
+
var fetchMock = vi.fn().mockResolvedValue(responseFor(validArtifact(), "https://evil.example/artifact.json"));
|
|
261
|
+
await expect(importCurriculumArtifactFromSearch(
|
|
262
|
+
"?curriculumArtifactUrl=https%3A%2F%2Fcdn.sanity.io%2Ffiles%2Fproject%2Fdataset%2Fwalking.json" +
|
|
263
|
+
"&curriculumArtifactSha256=" + "b".repeat(64),
|
|
264
|
+
{
|
|
265
|
+
baseUrl: "https://app.example/blocks-jr",
|
|
266
|
+
allowedOrigins: ["https://cdn.sanity.io"],
|
|
267
|
+
fetchImplementation: fetchMock,
|
|
268
|
+
}
|
|
269
|
+
)).rejects.toThrow(/redirect origin/);
|
|
270
|
+
expect(importPackageMock).not.toHaveBeenCalled();
|
|
271
|
+
});
|
|
272
|
+
});
|