@nutin/cli 1.2.0 → 1.2.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +63 -30
  2. package/README.md +9 -8
  3. package/lib/index.mjs +6 -6
  4. package/lib/src/file-generator.mjs +18 -5
  5. package/lib/src/package-manager.mjs +33 -21
  6. package/lib/src/prompts.mjs +2 -2
  7. package/lib/src/utils.mjs +2 -0
  8. package/package.json +4 -3
  9. package/templates/base/.dockerignore.hbs +2 -1
  10. package/templates/base/builder.config.js.hbs +28 -0
  11. package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +11 -12
  12. package/templates/base/src/app/dom.test.js.hbs +8 -1
  13. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +3 -0
  14. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.ts.hbs +3 -0
  15. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +2 -2
  16. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +3 -3
  17. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +2 -2
  18. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +4 -3
  19. package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +1 -1
  20. package/templates/base/src/index.html.hbs +1 -0
  21. package/templates/base/src/styles/main.scss.hbs +2 -0
  22. package/templates/base/tools/builder/builder.js.hbs +10 -7
  23. package/templates/base/tools/builder/core/copy-static.js.hbs +27 -2
  24. package/templates/base/tools/builder/core/esbuild.js.hbs +5 -15
  25. package/templates/base/tools/builder/core/finalize-build.js.hbs +2 -2
  26. package/templates/base/tools/builder/core/minify-html.js.hbs +2 -2
  27. package/templates/base/tools/builder/core/{sass.config.js.hbs → sass.js.hbs} +5 -7
  28. package/templates/base/tools/builder/core/tsc.js.hbs +8 -4
  29. package/templates/base/tools/builder/core/validate-html.js.hbs +19 -14
  30. package/templates/base/tools/deployment/Dockerfile.hbs +22 -0
  31. package/templates/base/tools/deployment/gzip.conf.hbs +19 -0
  32. package/templates/base/tools/deployment/nginx.conf.hbs +78 -0
  33. package/templates/base/tools/dev/dev-serve.js.hbs +32 -0
  34. package/templates/base/tools/{watcher.js.hbs → dev/watcher.js.hbs} +1 -1
  35. package/templates/base/tools/generator/generator.js.hbs +13 -5
  36. package/templates/base/tools/generator/handle-file.js.hbs +15 -8
  37. package/templates/base/tools/utils/export-flags.js.hbs +2 -1
  38. package/templates/base/tools/utils/index.js.hbs +4 -1
  39. package/templates/base/tools/utils/run-command.js.hbs +10 -3
  40. package/templates/base/tools/utils/run-script.js.hbs +1 -1
  41. package/templates/features/i18n/src/core/config.ts.hbs +3 -0
  42. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +2 -0
  43. package/templates/features/i18n/src/core/services/i18n/i18n.ts.hbs +10 -1
  44. package/templates/features/i18n/src/core/services/i18n/languages.ts.hbs +7 -0
  45. package/templates/features/i18n/tools/builder/core/build-i18n.js.hbs +2 -2
  46. package/templates/features/i18n/tools/utils/languages.js.hbs +7 -0
  47. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
  48. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  49. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +1 -1
  50. package/templates/features/testinNutin/{test → testin-nutin}/core/index.js.hbs +1 -1
  51. package/templates/features/testinNutin/testin-nutin/core/src/globals/assertion-lib.js.hbs +136 -0
  52. package/templates/features/testinNutin/testin-nutin/core/src/globals/jsdom-setup.js.hbs +101 -0
  53. package/templates/features/testinNutin/{test → testin-nutin}/core/src/globals/register-test-globals.js.hbs +15 -1
  54. package/templates/features/testinNutin/testin-nutin/core/src/globals/spyon.js.hbs +59 -0
  55. package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/print.js.hbs +3 -1
  56. package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/test-discovery.js.hbs +5 -1
  57. package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/test-queue.js.hbs +3 -1
  58. package/templates/features/testinNutin/{test → testin-nutin}/core/tests/assertions.test.js.hbs +26 -2
  59. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +35 -0
  60. package/templates/features/testinNutin/{test → testin-nutin}/watch-tests.js.hbs +1 -1
  61. package/templates/features/testinNutin/testin-nutin.config.js.hbs +14 -0
  62. package/templates/base/Dockerfile.hbs +0 -33
  63. package/templates/base/nginx.conf.hbs +0 -43
  64. package/templates/base/tools/builder/docs.md.hbs +0 -9
  65. package/templates/base/tools/dev.js.hbs +0 -43
  66. package/templates/features/testinNutin/test/core/src/globals/assertion-lib.js.hbs +0 -118
  67. package/templates/features/testinNutin/test/core/src/globals/spyon.js.hbs +0 -22
  68. package/templates/features/testinNutin/test/core/src/jsdom-setup.js.hbs +0 -12
  69. package/templates/features/testinNutin/test/docs.md.hbs +0 -39
  70. package/templates/features/testinNutin/test/runner.js.hbs +0 -36
  71. /package/templates/base/tools/{serve.js.hbs → dev/serve.js.hbs} +0 -0
  72. /package/templates/features/testinNutin/{test → testin-nutin}/core/src/utils/queue.js.hbs +0 -0
  73. /package/templates/features/testinNutin/{test → testin-nutin}/core/tests/globals-each-all.test.js.hbs +0 -0
  74. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/create-mock-method.js.hbs +0 -0
  75. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-event-bus.js.hbs +0 -0
  76. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-http-client.js.hbs +0 -0
  77. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-i18n.js.hbs +0 -0
  78. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-router.js.hbs +0 -0
  79. /package/templates/features/testinNutin/{test → testin-nutin}/mocks/mock-store.js.hbs +0 -0
@@ -72,7 +72,7 @@ async function removeJsonFiles(directory) {
72
72
 
73
73
  await removeEmptyDirectories(directory);
74
74
 
75
- if (isVerbose) print.info(`✅ JSON files cleanup complete.`);
75
+ if (isVerbose) print.boldInfo(`JSON files cleanup complete.`);
76
76
  }
77
77
 
78
78
  async function removeEmptyDirectories(directory) {
@@ -121,7 +121,7 @@ async function mergeJson() {
121
121
  `${destDir}/${locale}.json`,
122
122
  JSON.stringify(combined, null, 2)
123
123
  );
124
- print.info(`✅ Json files for locale ${locale} merged successfully`);
124
+ if (isVerbose) print.boldInfo(`Json files for locale ${locale} merged successfully`);
125
125
  }
126
126
  removeJsonFiles(PATHS.tempApp);
127
127
  }
@@ -0,0 +1,7 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ const langsPath = path.join(process.cwd(), 'config', 'languages.json');
5
+ const langs = JSON.parse(fs.readFileSync(langsPath, 'utf-8'));
6
+ export const LANGUAGES = langs.languages;
7
+ export const DEFAULT_LANGUAGE = langs.defaultLanguage;
@@ -1,3 +0,0 @@
1
- // global components style export
2
-
3
- // @forward "my-component";
@@ -40,7 +40,7 @@ async function mergeTemplates() {
40
40
  print.boldError(`ERROR: Failed to read ${htmlFilename}. ${err.message}`);
41
41
  }
42
42
  }
43
- print.info(`HTML templates minified and merged in scripts.`)
43
+ if (isVerbose) print.info(`HTML templates minified and merged in scripts.\n`)
44
44
  }
45
45
 
46
46
  mergeTemplates().catch((err) => {
@@ -1,4 +1,4 @@
1
- export * from './src/jsdom-setup.js';
1
+ export * from './src/globals/jsdom-setup.js';
2
2
  export * from './src/globals/register-test-globals.js';
3
3
  export * from './src/utils/print.js';
4
4
  export * from './src/utils/test-discovery.js';
@@ -0,0 +1,136 @@
1
+ global.expect = (actual) => {
2
+ const expectObj = {
3
+ toBe(expected) {
4
+ if (actual !== expected) {
5
+ throw new Error(`Expected ${actual} to be strictly equal to ${expected}`);
6
+ }
7
+ },
8
+ toEqual(expected) {
9
+ const deepEqual = (a, b) => {
10
+ if (a === b) return true;
11
+ if (typeof a !== typeof b) return false;
12
+ if (Array.isArray(a) && Array.isArray(b)) {
13
+ return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
14
+ }
15
+ if (a && b && typeof a === 'object') {
16
+ const keysA = Object.keys(a);
17
+ const keysB = Object.keys(b);
18
+ if (keysA.length !== keysB.length) return false;
19
+ return keysA.every(k => deepEqual(a[k], b[k]));
20
+ }
21
+ return false;
22
+ };
23
+
24
+ if (!deepEqual(actual, expected)) {
25
+ throw new Error(`Expected ${JSON.stringify(actual)} to deeply equal ${JSON.stringify(expected)}`);
26
+ }
27
+ },
28
+ toContain(substring) {
29
+ if (!actual.includes(substring)) {
30
+ throw new Error(`Expected '${actual}' to contain '${substring}'`);
31
+ }
32
+ },
33
+ toBeTruthy() {
34
+ if (!actual) {
35
+ throw new Error(`Expected ${actual} to be truthy`);
36
+ }
37
+ },
38
+ toBeFalsy() {
39
+ if (actual) {
40
+ throw new Error(`Expected ${actual} to be falsy`);
41
+ }
42
+ },
43
+ toBeDefined() {
44
+ if (actual === null || actual === undefined) {
45
+ throw new Error(`Expected value to be defined, but got ${actual}`);
46
+ }
47
+ },
48
+ toBeUndefined() {
49
+ if (actual !== null && actual !== undefined) {
50
+ throw new Error(`Expected value to be undefined, but got ${actual}`);
51
+ }
52
+ },
53
+ toBeInstanceOf(constructor) {
54
+ if (!(actual instanceof constructor)) {
55
+ throw new Error(`Expected ${actual} to be instance of ${constructor.name}`);
56
+ }
57
+ },
58
+ toHaveBeenCalled() {
59
+ if (!actual?.calls || actual.calls.length === 0) {
60
+ throw new Error(`Expected function to have been called, but it was not`);
61
+ }
62
+ },
63
+ toHaveBeenCalledWith(...expectedArgs) {
64
+ const deepEqual = (a, b) => {
65
+ if (a === b) return true;
66
+ if (typeof a !== typeof b) return false;
67
+
68
+ if (Array.isArray(a) && Array.isArray(b)) {
69
+ return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
70
+ }
71
+
72
+ if (a && b && typeof a === 'object') {
73
+ const keysA = Object.keys(a);
74
+ const keysB = Object.keys(b);
75
+ if (keysA.length !== keysB.length) return false;
76
+ return keysA.every(k => deepEqual(a[k], b[k]));
77
+ }
78
+
79
+ return false;
80
+ };
81
+
82
+ const matched = actual?.calls?.some(args =>
83
+ args.length === expectedArgs.length &&
84
+ args.every((arg, i) => deepEqual(arg, expectedArgs[i]))
85
+ );
86
+
87
+ if (!matched) {
88
+ throw new Error(
89
+ `Expected function to have been called with ${JSON.stringify(expectedArgs)}, but it was not.\n` +
90
+ `Actual calls:\n${JSON.stringify(actual?.calls, null, 2)}`
91
+ );
92
+ }
93
+ },
94
+ toBeLessThan(expected) {
95
+ if (actual > expected) {
96
+ throw new Error(`Expected ${actual} to be less than ${expected}`)
97
+ }
98
+ },
99
+ toBeGreaterThan(expected) {
100
+ if (actual < expected) {
101
+ throw new Error(`Expected ${actual} to be greater than ${expected}`)
102
+ }
103
+ },
104
+ toThrow(expectedMessage) {
105
+ let threw = false;
106
+ try {
107
+ actual();
108
+ } catch (err) {
109
+ threw = true;
110
+ if (expectedMessage && !err.message.includes(expectedMessage)) {
111
+ throw new Error(`Expected error message to include "${expectedMessage}", but got "${err.message}"`);
112
+ }
113
+ }
114
+ if (!threw) {
115
+ throw new Error(`Expected function to throw, but it did not`);
116
+ }
117
+ },
118
+ };
119
+
120
+ // 'not' variants
121
+ expectObj.not = {};
122
+ for (const [name, fn] of Object.entries(expectObj)) {
123
+ if (name === 'not') continue;
124
+ if (typeof fn !== 'function') continue;
125
+
126
+ expectObj.not[name] = (...args) => {
127
+ try {
128
+ fn(...args);
129
+ } catch {
130
+ return;
131
+ }
132
+ throw new Error(`Expected NOT: ${name}(${JSON.stringify(args)})`);
133
+ };
134
+ }
135
+ return expectObj;
136
+ };
@@ -0,0 +1,101 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { JSDOM } from 'jsdom';
4
+ import config from '#root/testin-nutin.config.js';
5
+ let currentDom = null;
6
+
7
+ export function setupJsdom(htmlPath = 'dist/src/index.html') {
8
+ const html = fs.readFileSync(path.resolve(process.cwd(), htmlPath), 'utf8');
9
+
10
+ const {
11
+ runScripts,
12
+ resources,
13
+ freezeGlobals,
14
+ pretendToBeVisual,
15
+ } = config.jsdomOptions;
16
+
17
+ const dom = new JSDOM(html, {
18
+ url: 'http://localhost',
19
+ runScripts: runScripts ? 'dangerously' : undefined,
20
+ resources: resources ? 'usable' : undefined,
21
+ pretendToBeVisual,
22
+ });
23
+
24
+ currentDom = dom;
25
+
26
+ global.window = dom.window;
27
+ global.document = dom.window.document;
28
+
29
+ // DOM globals
30
+ Object.getOwnPropertyNames(dom.window)
31
+ .filter(prop => !(prop in global))
32
+ .forEach(prop => {
33
+ global[prop] = dom.window[prop];
34
+ });
35
+
36
+ global.localStorage = dom.window.localStorage;
37
+
38
+ window.matchMedia ??= () => ({
39
+ matches: false,
40
+ addEventListener: () => {},
41
+ removeEventListener: () => {},
42
+ });
43
+
44
+ window.scrollTo ??= () => {};
45
+
46
+ window.requestAnimationFrame ??= (cb) => setTimeout(cb, 0);
47
+ window.cancelAnimationFrame ??= (id) => clearTimeout(id);
48
+
49
+ window.IntersectionObserver ??= class {
50
+ constructor() {}
51
+ observe() {}
52
+ unobserve() {}
53
+ disconnect() {}
54
+ };
55
+
56
+ window.ResizeObserver ??= class {
57
+ constructor() {}
58
+ observe() {}
59
+ unobserve() {}
60
+ disconnect() {}
61
+ };
62
+
63
+ if (freezeGlobals) {
64
+ Object.freeze(global.window);
65
+ Object.freeze(global.document);
66
+ }
67
+
68
+ return dom;
69
+ }
70
+
71
+ export function teardownJsdom() {
72
+ if (currentDom) {
73
+ try {
74
+ currentDom.window.close();
75
+ } catch {}
76
+ }
77
+
78
+ currentDom = null;
79
+
80
+ delete global.window;
81
+ delete global.document;
82
+ delete global.localStorage;
83
+ }
84
+
85
+ export function resetDom() {
86
+ if (!global.document) return;
87
+
88
+ document.body.innerHTML = '';
89
+ document.head.innerHTML = '<meta charset="UTF-8">';
90
+
91
+ if (global.localStorage) {
92
+ localStorage.clear();
93
+ }
94
+ }
95
+
96
+ export function flushPromises() {
97
+ return new Promise(setImmediate);
98
+ }
99
+
100
+ export const $ = (selector) => document.querySelector(selector);
101
+ export const $$ = (selector) => [...document.querySelectorAll(selector)];
@@ -1,4 +1,6 @@
1
1
  import { print, addTest } from '../../index.js';
2
+ import config from '#root/testin-nutin.config.js';
3
+ import { setupJsdom, teardownJsdom, resetDom, flushPromises } from './jsdom-setup.js';
2
4
  import './assertion-lib.js';
3
5
  import './spyon.js';
4
6
 
@@ -14,7 +16,7 @@ export function registerTestGlobals() {
14
16
  afterEach: null,
15
17
  afterAll: null
16
18
  };
17
- print.boldSuccess('📂 ' + name);
19
+ if (config.verbose) print.boldSuccess('📂 ' + name);
18
20
  fn();
19
21
  };
20
22
 
@@ -42,4 +44,16 @@ export function registerTestGlobals() {
42
44
  global.afterEach = (fn) => {
43
45
  currentSuiteHooks.afterEach = fn;
44
46
  };
47
+
48
+ global.setupJsdom = setupJsdom;
49
+ global.teardownJsdom = teardownJsdom;
50
+ global.resetDom = resetDom;
51
+ global.flushPromises = flushPromises;
52
+
53
+ global.$ = (selector) => document.querySelector(selector);
54
+ global.$$ = (selector) => [...document.querySelectorAll(selector)];
55
+
56
+ global.click = (el) => el.dispatchEvent(new window.MouseEvent("click", { bubbles: true }));
57
+ global.type = (el, text) => { el.value = text; el.dispatchEvent(new window.InputEvent("input", { bubbles: true })); };
58
+
45
59
  }
@@ -0,0 +1,59 @@
1
+ global.spyOn = (obj, methodName) => {
2
+ if (!obj || typeof methodName !== 'string') {
3
+ throw new Error('spyOn requires an object and a method name');
4
+ }
5
+
6
+ const originalFn = obj[methodName];
7
+
8
+ if (typeof originalFn !== 'function') {
9
+ throw new Error(`Cannot spy on ${methodName} – not a function`);
10
+ }
11
+
12
+ const calls = [];
13
+
14
+ function spyFunction(...args) {
15
+ calls.push(args.slice());
16
+ if (spyFunction._fake) {
17
+ return spyFunction._fake.apply(this, args);
18
+ }
19
+ if (spyFunction._hasReturn) {
20
+ return spyFunction._returnValue;
21
+ }
22
+ return originalFn.apply(this, args);
23
+ }
24
+
25
+ spyFunction._isSpy = true;
26
+ spyFunction._fake = null;
27
+ spyFunction._hasReturn = false;
28
+ spyFunction._returnValue = undefined;
29
+
30
+ obj[methodName] = spyFunction;
31
+
32
+ const handle = {
33
+ restore() {
34
+ if (obj[methodName] === spyFunction) {
35
+ obj[methodName] = originalFn;
36
+ }
37
+ },
38
+ get calls() {
39
+ return calls;
40
+ },
41
+ get callCount() {
42
+ return calls.length;
43
+ },
44
+ get lastCall() {
45
+ return calls.length ? calls[calls.length - 1] : undefined;
46
+ },
47
+ andCallFake(fn) {
48
+ spyFunction._fake = fn;
49
+ spyFunction._hasReturn = false;
50
+ },
51
+ andReturn(value) {
52
+ spyFunction._returnValue = value;
53
+ spyFunction._hasReturn = true;
54
+ spyFunction._fake = null;
55
+ }
56
+ };
57
+
58
+ return handle;
59
+ };
@@ -1,3 +1,5 @@
1
+ import config from '#root/testin-nutin.config.js';
2
+
1
3
  export function printSummary(passed, failed, time) {
2
4
  const totalTime = time.toFixed(2);
3
5
  const total = passed + failed;
@@ -17,7 +19,7 @@ export function printResults(results) {
17
19
 
18
20
  sortedResults.forEach(result => {
19
21
  if (result.status === 'passed') {
20
- console.log(`${chalk.green('✓')} ${chalk.cyan(`${result.suiteName}`)} - ${chalk.green(`${result.name}`)}`);
22
+ if (config.verbose ) console.log(`${chalk.green('✓')} ${chalk.cyan(`${result.suiteName}`)} - ${chalk.green(`${result.name}`)}`);
21
23
  } else {
22
24
  print.error(`✗ ${result.suiteName} - ${result.name}`);
23
25
  print.grayError(result.error);
@@ -3,9 +3,13 @@ import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
 
5
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const relativeToCwd = path.relative(process.cwd(), __dirname);
7
+ const levelsUp = relativeToCwd.split(path.sep).length-1;
8
+ const projectRoot = path.resolve(__dirname, ...Array(levelsUp).fill('..'));
6
9
 
7
10
  export function getTestFiles(dir) {
8
- const fullDir = path.resolve(__dirname,'../', '../', '../', dir);
11
+ const fullDir = path.join(projectRoot, dir);
12
+
9
13
  if (!fs.existsSync(fullDir)) return [];
10
14
 
11
15
  function findTestFilesRecursively(currentDir) {
@@ -34,9 +34,11 @@ export async function runQueuedTests() {
34
34
  previousSuiteAfterAll = test.afterAll;
35
35
  }
36
36
 
37
+ setupJsdom();
37
38
  if (test.beforeEach) await test.beforeEach();
38
39
  await test.testFn();
39
40
  if (test.afterEach) await test.afterEach();
41
+ teardownJsdom();
40
42
 
41
43
  passed++;
42
44
  results.push({
@@ -50,7 +52,7 @@ export async function runQueuedTests() {
50
52
  suiteName: test.suiteName,
51
53
  status: 'failed',
52
54
  name: test.testName,
53
- error: err.stack
55
+ error: err.stack
54
56
  });
55
57
  }
56
58
  }
@@ -8,11 +8,17 @@ describe('assertions', () => {
8
8
  it('should check if value matches expected', () => {
9
9
  const five = obj.add(2,3);
10
10
  const ten = obj.add(3,7);
11
+ const object = { five: five, ten: ten };
12
+ const arr = [ five, ten ];
11
13
 
12
14
  expect(five).toEqual(5);
13
- expect(five).toBe('5');
15
+ expect(five).toBe(5);
14
16
  expect(ten).not.toEqual(11);
15
- expect(ten).not.toBe('11');
17
+ expect(ten).not.toBe(11);
18
+ expect(object).toEqual({ five: five, ten: ten });
19
+ expect(object).not.toBe({ five: five, ten: ten });
20
+ expect(arr).toEqual([five, ten]);
21
+ expect(arr).not.toBe([five, ten]);
16
22
  });
17
23
  it('should check if function has been called', () => {
18
24
  const spy = spyOn(obj, 'add');
@@ -29,6 +35,12 @@ describe('assertions', () => {
29
35
  expect(null).toBeUndefined();
30
36
  expect(null).not.toBeDefined();
31
37
  });
38
+ it('should check for if an element is true / false', () => {
39
+ const isTrue = 1 == '1';
40
+ const isFalse = 1 === '1';
41
+ expect(isTrue).toBeTruthy();
42
+ expect(isFalse).toBeFalsy();
43
+ });
32
44
  it('should check if substring is contained within string', () => {
33
45
  const str1 = 'Creativity is allowing yourself to make mistakes.';
34
46
  const str2 = 'Art is knowing which ones to keep.';
@@ -40,7 +52,19 @@ describe('assertions', () => {
40
52
  });
41
53
  it('should check for instance', () => {
42
54
  const date = new Date();
55
+ const fn = () => {};
43
56
  expect(date).toBeInstanceOf(Date);
57
+ expect(fn).toBeInstanceOf(Function);
44
58
  expect(date).not.toBeInstanceOf(Function);
59
+ expect(fn).not.toBeInstanceOf(Date);
60
+ });
61
+ it('should check for error throw', () => {
62
+ const err = () => { throw new Error('message'); };
63
+ const notErr = () => {};
64
+ expect(err).toThrow();
65
+ expect(err).toThrow('message');
66
+ expect(err).not.toThrow('another message');
67
+ expect(notErr).not.toThrow();
68
+ expect(notErr).not.toThrow('messsage');
45
69
  });
46
70
  });
@@ -0,0 +1,35 @@
1
+ import { registerTestGlobals, getTestFiles, runQueuedTests, print } from './core/index.js';
2
+ import config from '#root/testin-nutin.config.js';
3
+
4
+ registerTestGlobals();
5
+
6
+ let testFiles = config.origins.flatMap(
7
+ origin => getTestFiles(`../${origin}`)
8
+ );
9
+
10
+ const args = process.argv.slice(2);
11
+ if (args.length) {
12
+ testFiles = testFiles.filter(file => args.some(f => file.includes(f)));
13
+ }
14
+
15
+ async function loadTestFile(file) {
16
+ setupJsdom();
17
+ await import(file);
18
+ teardownJsdom();
19
+ }
20
+
21
+ async function runTests() {
22
+ if (config.verbose) print.head('Test suites :');
23
+ for (const file of testFiles) {
24
+ try {
25
+ await loadTestFile(file);
26
+ } catch (error) {
27
+ print.boldError(`✗ ${file} failed to load:`);
28
+ print.grayError(error.stack);
29
+ }
30
+ }
31
+
32
+ await runQueuedTests();
33
+ }
34
+
35
+ runTests();
@@ -14,7 +14,7 @@ watcher.on('change', (filePath) => {
14
14
  console.clear();
15
15
  print.boldInfo(`\n🔄 File changed: ${path.relative(process.cwd(), filePath)}\n`);
16
16
 
17
- exec('node --experimental-modules test/runner.js', (err, stdout, stderr) => {
17
+ exec('node --experimental-modules testin-nutin/runner.js', (err, stdout, stderr) => {
18
18
  if (stdout) process.stdout.write(stdout);
19
19
  if (stderr) process.stderr.write(stderr);
20
20
  });
@@ -0,0 +1,14 @@
1
+ export default {
2
+ origins: [
3
+ 'src/app', // actual app code
4
+ // 'src/core', // toolkit core logic
5
+ // 'testin-nutin/core/tests', // test globals
6
+ ],
7
+ verbose: false,
8
+ jsdomOptions: {
9
+ runScripts: false, // or true ("dangerously") if needed
10
+ resources: false, // or true ("usable") if needed
11
+ freezeGlobals: false,
12
+ pretendToBeVisual: true,
13
+ }
14
+ }
@@ -1,33 +0,0 @@
1
- ARG NODE_VERSION=22.3.0
2
- ARG NGINX_VERSION=1.27-alpine
3
-
4
- FROM node:${NODE_VERSION} AS builder
5
- WORKDIR /usr/src/app
6
- COPY . .
7
- RUN {{ciCommand}} && \
8
- {{packageManager}} run build:prod
9
-
10
- FROM nginx:${NGINX_VERSION}
11
-
12
- RUN rm /etc/nginx/conf.d/default.conf
13
- COPY --from=builder /usr/src/app/nginx.conf /etc/nginx/conf.d/default.conf
14
-
15
- RUN sed -i '/http {/a \
16
- # Gzip compression\n\
17
- gzip on;\n\
18
- gzip_vary on;\n\
19
- gzip_proxied any;\n\
20
- gzip_comp_level 6;\n\
21
- gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;\n\
22
- gzip_min_length 1000;\n\
23
- ' /etc/nginx/nginx.conf && \
24
- touch /var/run/nginx.pid
25
-
26
- COPY --from=builder /usr/src/app/dist/src /usr/share/nginx/html
27
-
28
- EXPOSE 9090
29
-
30
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
31
- CMD curl -f http://localhost:9090/health || exit 1
32
-
33
- CMD ["nginx", "-g", "daemon off;"]
@@ -1,43 +0,0 @@
1
- server {
2
- listen 9090;
3
- server_name _;
4
- root /usr/share/nginx/html;
5
- index index.html;
6
-
7
- # Security headers
8
- add_header X-Frame-Options "SAMEORIGIN" always;
9
- add_header X-Content-Type-Options "nosniff" always;
10
- add_header X-XSS-Protection "1; mode=block" always;
11
- add_header Referrer-Policy "strict-origin-when-cross-origin" always;
12
- add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
13
-
14
- # Content Security Policy - ADJUST BASED ON YOUR APP'S NEEDS
15
- add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self';" always;
16
-
17
- location / {
18
- try_files $uri $uri/ /index.html;
19
- }
20
-
21
- # Static assets with long cache
22
- location ~* \.(js|css|png|jpg|jpeg|gif|avif|ico|svg|woff|woff2|ttf|eot)$ {
23
- expires 1y;
24
- add_header Cache-Control "public, immutable";
25
- }
26
-
27
- # HTML files - no cache
28
- location ~* \.html$ {
29
- expires -1;
30
- add_header Cache-Control "no-cache, no-store, must-revalidate";
31
- add_header Pragma "no-cache";
32
- }
33
-
34
- # Health check endpoint
35
- location /health {
36
- access_log off;
37
- return 200 "healthy\n";
38
- add_header Content-Type text/plain;
39
- }
40
-
41
- # Hide nginx version
42
- server_tokens off;
43
- }
@@ -1,9 +0,0 @@
1
- - Adding additnal file formats to the build
2
- Default file formats are `"html" "json" "ttf" "avif" "png" "jpg"`.
3
- Only the files in src/ that match one of the format are copied.
4
- In `tools/builder/variables/file-types.js`, add desired format to existing list. Adding a file extension with no match in src/ will have no effect.
5
-
6
- - Build Sequence
7
-
8
-
9
- [**Back to index**](../../README.md)
@@ -1,43 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { spawn } from 'child_process';
4
- import { print, runCommand } from './utils/index.js';
5
-
6
- function run(command, args = [], label = '') {
7
- return new Promise((resolve, reject) => {
8
- print.info(`\n▶ ${label || command}...`);
9
-
10
- const child = spawn(command, args, { stdio: 'inherit', shell: true });
11
-
12
- child.on('close', (code) => {
13
- if (code === 0) resolve();
14
- else reject(new Error(`${command} exited with code ${code}`));
15
- });
16
- });
17
- }
18
-
19
- async function startDev() {
20
- try {
21
- console.clear();
22
- print.boldHead('🚀 Starting Dev Environment...');
23
-
24
- await run('{{packageManager}}', ['run', 'build'], 'Running initial build');
25
-
26
- const serve = spawn('{{packageManager}}', ['run', 'serve:only'], { stdio: 'inherit', shell: true });
27
- const watcher = spawn('node', ['tools/watcher.js'], { stdio: 'inherit', shell: true });
28
-
29
- print.boldSuccess('\nDev server and watcher running!\n');
30
-
31
- serve.on('close', (code) => {
32
- print.error(`live-server exited with code ${code}`);
33
- process.exit(code);
34
- });
35
- watcher.on('close', (code) => {
36
- print.error(`watcher exited with code ${code}`);
37
- });
38
- } catch (err) {
39
- print.boldError(`Dev startup failed: ${err.message}`);
40
- }
41
- }
42
-
43
- startDev();