jspacker - jspacker_test.go
1 package jspacker
2
3 import (
4 "fmt"
5 "os"
6 "testing"
7
8 "vimagination.zapto.org/javascript"
9 "vimagination.zapto.org/parser"
10 )
11
12 type loader map[string]string
13
14 func (l loader) load(url string) (*javascript.Module, error) {
15 d, ok := l[url]
16 if !ok {
17 return nil, os.ErrNotExist
18 }
19 tks := parser.NewStringTokeniser(d)
20 return javascript.ParseModule(&tks)
21 }
22
23 func TestPackage(t *testing.T) {
24 for n, test := range [...]struct {
25 Input loader
26 Output string
27 Options []Option
28 }{
29 { // 1
30 loader{"/a.js": "1"},
31 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\n1;",
32 []Option{File("/a.js")},
33 },
34 { // 2
35 loader{"/a.js": "1"},
36 "\n\n1;",
37 []Option{File("/a.js"), NoExports},
38 },
39 { // 3
40 loader{
41 "/a.js": "import {c} from './b.js'; console.log(c)",
42 "/b.js": "export const c = 1",
43 },
44 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"c\", () => b_c]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nconst b_c = 1;\n\nconsole.log(b_c);",
45 []Option{File("/a.js")},
46 },
47 { // 4
48 loader{
49 "/a.js": "import {d} from './b.js'; console.log(d)",
50 "/b.js": "export {d} from './c.js'",
51 "/c.js": "export const d = 1",
52 },
53 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"d\", () => c_d]], [\"/c.js\", [\"d\", () => c_d]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nconst c_d = 1;\n\nconsole.log(c_d);",
54 []Option{File("/a.js")},
55 },
56 { // 5
57 loader{
58 "/a.js": "import {c as d} from './b.js'; console.log(d)",
59 "/b.js": "export const c = 1",
60 },
61 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"c\", () => b_c]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nconst b_c = 1;\n\nconsole.log(b_c);",
62 []Option{File("/a.js")},
63 },
64 { // 6
65 loader{
66 "/a.js": "import {f as g} from './b.js'; console.log(g)",
67 "/b.js": "export {e as f} from './c.js'",
68 "/c.js": "const d = 1;export {d as e}",
69 },
70 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"f\", () => c_d]], [\"/c.js\", [\"e\", () => c_d]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nconst c_d = 1;\n\nconsole.log(c_d);",
71 []Option{File("/a.js")},
72 },
73 { // 7
74 loader{
75 "/a.js": "import c from './b.js'; console.log(c)",
76 "/b.js": "export default 1",
77 },
78 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"default\", () => b_default]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nconst b_default = 1;\n\nconsole.log(b_default);",
79 []Option{File("/a.js")},
80 },
81 { // 8
82 loader{
83 "/a.js": "import c from './b.js'; console.log(c)",
84 "/b.js": "export {default} from './c.js'",
85 "/c.js": "export default 1",
86 },
87 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"default\", () => c_default]], [\"/c.js\", [\"default\", () => c_default]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nconst c_default = 1;\n\nconsole.log(c_default);",
88 []Option{File("/a.js")},
89 },
90 { // 9
91 loader{
92 "/a.js": "import {d} from './b/b.js'; console.log(d)",
93 "/b/b.js": "export {d} from '../c/c.js'",
94 "/c/c.js": "export const d = 1",
95 },
96 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b/b.js\", [\"d\", () => c_d]], [\"/c/c.js\", [\"d\", () => c_d]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nconst c_d = 1;\n\nconsole.log(c_d);",
97 []Option{File("/a.js")},
98 },
99 { // 10
100 loader{
101 "/a.js": "import * as e from './b/b.js'; console.log(e.d)",
102 "/b/b.js": "export * from '../c/c.js'",
103 "/c/c.js": "export const d = 1",
104 },
105 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b/b.js\", [\"d\", () => c_d]], [\"/c/c.js\", [\"d\", () => c_d]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nconst c_d = 1;\n\nconst e = await include(\"/b/b.js\", true);\n\nconsole.log(e.d);",
106 []Option{File("/a.js")},
107 },
108 { // 11
109 loader{
110 "/a.js": "import {b, c} from './b/b.js'; console.log(b, c)",
111 "/b/b.js": "import {c} from '../c/c.js';const b = 1;export {b, c};",
112 "/c/c.js": "import {b} from '../b/b.js';const c = 2;export {b, c};",
113 },
114 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b/b.js\", [\"b\", () => b_b], [\"c\", () => c_c]], [\"/c/c.js\", [\"b\", () => b_b], [\"c\", () => c_c]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nconst c_c = 2;\n\nconst b_b = 1;\n\nconsole.log(b_b, c_c);",
115 []Option{File("/a.js")},
116 },
117 { // 12
118 loader{
119 "/a.js": "import {a as ba, b as bb} from './b/b.js'; import {a as ca, b as cb} from './c/c.js'; console.log(ba, bb, ca, cb)",
120 "/b/b.js": "export * from '../c/c.js';export const a = 1;",
121 "/c/c.js": "export * from '../b/b.js';export const b = 2;",
122 },
123 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b/b.js\", [\"a\", () => b_a], [\"b\", () => c_b]], [\"/c/c.js\", [\"a\", () => b_a], [\"b\", () => c_b]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nconst c_b = 2;\n\nconst b_a = 1;\n\nconsole.log(b_a, c_b, b_a, c_b);",
124 []Option{File("/a.js")},
125 },
126 { // 13
127 loader{
128 "/a.js": "import {a, b, c} from './b/b.js'; console.log(a, b, c)",
129 "/b/b.js": "export * from '../c/c.js';export const a = 1;",
130 "/c/c.js": "export const a = 2, b = 3, c = 4",
131 },
132 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b/b.js\", [\"a\", () => b_a], [\"b\", () => c_b], [\"c\", () => c_c]], [\"/c/c.js\", [\"a\", () => c_a], [\"b\", () => c_b], [\"c\", () => c_c]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nconst c_a = 2, c_b = 3, c_c = 4;\n\nconst b_a = 1;\n\nconsole.log(b_a, c_b, c_c);",
133 []Option{File("/a.js")},
134 },
135 { // 14
136 loader{
137 "/a.js": "import {a} from '/b.js'; console.log(a)",
138 "/b.js": "export * from '/c.js';",
139 "/c.js": "export let a = 1;",
140 },
141 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"a\", () => c_a]], [\"/c.js\", [\"a\", () => c_a]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nlet c_a = 1;\n\nconsole.log(c_a);",
142 []Option{File("/a.js")},
143 },
144 { // 15
145 loader{
146 "/a.js": "import {a} from '/b.js'; console.log(a)",
147 "/b.js": "export * from '/c.js';",
148 "/c.js": "export var a = 1;",
149 },
150 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"a\", () => c_a]], [\"/c.js\", [\"a\", () => c_a]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nvar c_a = 1;\n\nconsole.log(c_a);",
151 []Option{File("/a.js")},
152 },
153 { // 16
154 loader{
155 "/a.js": "import fn from './b.js'; fn()",
156 "/b.js": "export default function () {}",
157 },
158 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"default\", () => b_default]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nfunction b_default() {}\n\nb_default();",
159 []Option{File("/a.js")},
160 },
161 { // 17
162 loader{
163 "/a.js": "import cl from './b.js'; new cl()",
164 "/b.js": "export default class {}",
165 },
166 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"default\", () => b_default]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nclass b_default {}\n\nnew b_default();",
167 []Option{File("/a.js")},
168 },
169 { // 18
170 loader{
171 "/a.js": "import vr from './b.js'; console.log(vr)",
172 "/b.js": "const b = 1; export default b",
173 },
174 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"default\", () => b_default]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nconst b_b = 1;\n\nconst b_default = b_b;\n\nconsole.log(b_default);",
175 []Option{File("/a.js")},
176 },
177 { // 19
178 loader{
179 "/a.js": "import vr from './b.js'; console.log(vr)",
180 "/b.js": "export default class MyClass {}",
181 },
182 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"default\", () => b_default]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nclass b_default {}\n\nconsole.log(b_default);",
183 []Option{File("/a.js")},
184 },
185 { // 20
186 loader{
187 "/a.js": "import vr from './b.js'; console.log(vr)",
188 "/b.js": "export default class MyClass {static INSTANCE = new MyClass();}",
189 },
190 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"default\", () => b_default]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nclass b_default {\n\tstatic INSTANCE = new b_default();\n}\n\nconsole.log(b_default);",
191 []Option{File("/a.js")},
192 },
193 { // 21
194 loader{
195 "/a.js": "import vr from './b.js'; console.log(vr)",
196 "/b.js": "export default function aaa() {}",
197 },
198 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"default\", () => b_default]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nfunction b_default() {}\n\nconsole.log(b_default);",
199 []Option{File("/a.js")},
200 },
201 { // 22
202 loader{
203 "/a.js": "import vr from './b.js'; console.log(vr)",
204 "/b.js": "export default function aaa() {aaa()}",
205 },
206 "Object.defineProperty(globalThis, \"include\", {value: (() => {\n\tconst imports = new Map([[\"/a.js\"], [\"/b.js\", [\"default\", () => b_default]]].map(([url, ...props]) => [url, Object.freeze(Object.defineProperties({}, Object.fromEntries(props.map(([prop, get]) => [prop, {enumerable: true, get}]))))]));\n\treturn url => Promise.resolve(imports.get(url) ?? import(url));\n})()});\n\nfunction b_default() {\n\tb_default();\n}\n\nconsole.log(b_default);",
207 []Option{File("/a.js")},
208 },
209 } {
210 s, err := Package(append(test.Options, Loader(test.Input.load))...)
211 if err != nil {
212 t.Fatalf("test %d: unexpected err: %s", n+1, err)
213 }
214 output := fmt.Sprintf("%s", s)
215 if output != test.Output {
216 t.Errorf("test %d: expecting output: %q\ngot: %q", n+1, test.Output, output)
217 }
218 }
219 }
220
221 func TestPlugin(t *testing.T) {
222 for n, test := range [...]struct {
223 Input string
224 URL string
225 Output string
226 }{
227 { // 1
228 "import a from './b.js';console.log(a)",
229 "/a.js",
230 "const a_ = await include(\"/b.js\");\n\nconsole.log(a_.default);",
231 },
232 { // 2
233 "import a from '../b.js';console.log(a)",
234 "/a/a.js",
235 "const a_ = await include(\"/b.js\");\n\nconsole.log(a_.default);",
236 },
237 { // 3
238 "import a, {b, c} from './b.js';console.log(a, b, c)",
239 "/a.js",
240 "const a_ = await include(\"/b.js\");\n\nconsole.log(a_.default, a_.b, a_.c);",
241 },
242 { // 4
243 "import * as a from './b.js';console.log(a)",
244 "/a.js",
245 "const a_ = await include(\"/b.js\");\n\nconsole.log(a_);",
246 },
247 } {
248 tks := parser.NewStringTokeniser(test.Input)
249 m, err := javascript.ParseModule(&tks)
250 if err != nil {
251 t.Fatalf("test %d: unexpected err: %s", n+1, err)
252 }
253 s, err := Plugin(m, test.URL)
254 if err != nil {
255 t.Fatalf("test %d: unexpected err: %s", n+1, err)
256 }
257 output := fmt.Sprintf("%s", s)
258 if output != test.Output {
259 t.Errorf("test %d: expecting output: %q\ngot: %q", n+1, test.Output, output)
260 }
261 }
262 }
263