@jterrazz/typescript 6.1.0 → 6.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/bin/commands/check.sh +110 -19
- package/lib/merge-knip-config.js +1 -1
- package/package.json +16 -4
- package/presets/oxlint/base.js +13 -0
- package/src/oxlint.d.ts +15 -0
- package/src/oxlint.js +56 -0
- package/src/oxlint.test.ts +79 -0
package/bin/commands/check.sh
CHANGED
|
@@ -33,16 +33,30 @@ find_binary() {
|
|
|
33
33
|
fi
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
36
|
+
# Type checking uses the official TypeScript 7 Go compiler, pulled in through
|
|
37
|
+
# the per-platform @typescript/typescript-* packages instead of a second
|
|
38
|
+
# package named "typescript": typedoc and eslint-plugin-perfectionist load
|
|
39
|
+
# the JS API from the "typescript" name (v6 here), and any typescript@7 in
|
|
40
|
+
# the tree can hijack that lookup under pnpm's hoist fallback.
|
|
41
41
|
find_tsc() {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
local os arch
|
|
43
|
+
case "$(uname -s)" in
|
|
44
|
+
Darwin) os="darwin" ;;
|
|
45
|
+
Linux) os="linux" ;;
|
|
46
|
+
MINGW*|MSYS*|CYGWIN*) os="win32" ;;
|
|
47
|
+
*) os="linux" ;;
|
|
48
|
+
esac
|
|
49
|
+
case "$(uname -m)" in
|
|
50
|
+
arm64|aarch64) arch="arm64" ;;
|
|
51
|
+
armv7l) arch="arm" ;;
|
|
52
|
+
*) arch="x64" ;;
|
|
53
|
+
esac
|
|
54
|
+
|
|
55
|
+
local pkg="@typescript/typescript-$os-$arch"
|
|
56
|
+
if [ -x "$PACKAGE_ROOT/node_modules/$pkg/lib/tsc" ]; then
|
|
57
|
+
echo "$PACKAGE_ROOT/node_modules/$pkg/lib/tsc"
|
|
58
|
+
elif [ -x "$PACKAGE_ROOT/../../$pkg/lib/tsc" ]; then
|
|
59
|
+
echo "$PACKAGE_ROOT/../../$pkg/lib/tsc"
|
|
46
60
|
else
|
|
47
61
|
find_binary tsc
|
|
48
62
|
fi
|
|
@@ -52,6 +66,41 @@ TSC=$(find_tsc)
|
|
|
52
66
|
OXLINT=$(find_binary oxlint)
|
|
53
67
|
OXFMT=$(find_binary oxfmt)
|
|
54
68
|
KNIP=$(find_binary knip)
|
|
69
|
+
CHECKER=$(find_binary jterrazz-test-check)
|
|
70
|
+
|
|
71
|
+
# The @jterrazz/test conventions checker (D4 tokens, C8/C9 fixtures) runs only when the
|
|
72
|
+
# consuming project depends on @jterrazz/test — auto-detected from its package.json.
|
|
73
|
+
project_uses_jterrazz_test() {
|
|
74
|
+
[ -f "package.json" ] || return 1
|
|
75
|
+
node -e 'const p=require("./package.json");const d={...p.dependencies,...p.devDependencies,...p.peerDependencies};process.exit(d["@jterrazz/test"]?0:1)' 2>/dev/null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# The @jterrazz/test oxlint plugin is ESM-only. A CommonJS oxlint config silently drops
|
|
79
|
+
# it (oxlint prints a load warning and still exits 0) — none of the jterrazz/* rules run.
|
|
80
|
+
# Warn loudly when that pitfall is detectable.
|
|
81
|
+
warn_cjs_oxlint_config() {
|
|
82
|
+
local cfg=""
|
|
83
|
+
for c in oxlint.config.ts oxlint.config.mjs oxlint.config.cjs oxlint.config.js; do
|
|
84
|
+
[ -f "$c" ] && { cfg="$c"; break; }
|
|
85
|
+
done
|
|
86
|
+
[ -z "$cfg" ] && return 0
|
|
87
|
+
|
|
88
|
+
local is_cjs=false
|
|
89
|
+
case "$cfg" in
|
|
90
|
+
*.cjs) is_cjs=true ;;
|
|
91
|
+
*.js)
|
|
92
|
+
if ! node -e 'process.exit(require("./package.json").type==="module"?0:1)' 2>/dev/null; then
|
|
93
|
+
is_cjs=true
|
|
94
|
+
fi
|
|
95
|
+
;;
|
|
96
|
+
esac
|
|
97
|
+
|
|
98
|
+
if [ "$is_cjs" = true ]; then
|
|
99
|
+
printf "${RED} WARNING ${NC} @jterrazz/test is installed but %s is CommonJS.\n" "$cfg"
|
|
100
|
+
printf " The @jterrazz/test oxlint plugin is ESM-only and will be SILENTLY DROPPED —\n"
|
|
101
|
+
printf " none of the jterrazz/* rules will run. Switch to an ESM config (oxlint.config.ts or .mjs).\n\n"
|
|
102
|
+
fi
|
|
103
|
+
}
|
|
55
104
|
|
|
56
105
|
# Parse command and args
|
|
57
106
|
COMMAND=""
|
|
@@ -95,6 +144,10 @@ run_checks() {
|
|
|
95
144
|
|
|
96
145
|
printf "${CYAN_BG}${BRIGHT_WHITE} START ${NC} ${LABEL}\n"
|
|
97
146
|
|
|
147
|
+
if project_uses_jterrazz_test; then
|
|
148
|
+
warn_cjs_oxlint_config
|
|
149
|
+
fi
|
|
150
|
+
|
|
98
151
|
# Run all tools in parallel
|
|
99
152
|
"$TSC" --noEmit > "$tmp_dir/type.log" 2>&1 &
|
|
100
153
|
local type_pid=$!
|
|
@@ -128,33 +181,71 @@ run_checks() {
|
|
|
128
181
|
knip_pid=$!
|
|
129
182
|
fi
|
|
130
183
|
|
|
184
|
+
# Conventions checker: only in check mode, only when the project uses @jterrazz/test
|
|
185
|
+
# and has a specs/ directory to validate.
|
|
186
|
+
local checker_pid=""
|
|
187
|
+
local checker_status=0
|
|
188
|
+
if [ "$FIX_MODE" = false ] && [ -d "specs" ] && project_uses_jterrazz_test; then
|
|
189
|
+
"$CHECKER" specs > "$tmp_dir/checker.log" 2>&1 &
|
|
190
|
+
checker_pid=$!
|
|
191
|
+
fi
|
|
192
|
+
|
|
131
193
|
# Wait and collect statuses
|
|
132
194
|
wait $type_pid; local type_status=$?
|
|
133
195
|
wait $lint_pid; local lint_status=$?
|
|
134
196
|
wait $format_pid; local format_status=$?
|
|
135
197
|
[ -n "$knip_pid" ] && { wait $knip_pid; knip_status=$?; }
|
|
198
|
+
[ -n "$checker_pid" ] && { wait $checker_pid; checker_status=$?; }
|
|
136
199
|
|
|
137
|
-
# Print results
|
|
200
|
+
# Print results — quiet on success, verbose on failure: a tool's captured log
|
|
201
|
+
# is shown only when it failed, so green output stays byte-identical across
|
|
202
|
+
# platforms (some tool builds print success chatter on Linux but not macOS).
|
|
138
203
|
printf "\n${CYAN_BG}${BRIGHT_WHITE} RUN ${NC} TypeScript Check\n\n"
|
|
139
|
-
[ -
|
|
140
|
-
|
|
204
|
+
if [ $type_status -ne 0 ]; then
|
|
205
|
+
[ -s "$tmp_dir/type.log" ] && cat "$tmp_dir/type.log"
|
|
206
|
+
printf "${RED}✗ Failed with exit code %d${NC}\n" $type_status
|
|
207
|
+
else
|
|
208
|
+
printf "${GREEN}✓ Passed${NC}\n"
|
|
209
|
+
fi
|
|
141
210
|
|
|
142
211
|
local lint_label="Oxlint Check"
|
|
143
212
|
[ "$FIX_MODE" = true ] && lint_label="Oxlint Fix"
|
|
144
213
|
printf "\n${CYAN_BG}${BRIGHT_WHITE} RUN ${NC} ${lint_label}\n\n"
|
|
145
|
-
[ -
|
|
146
|
-
|
|
214
|
+
if [ $lint_status -ne 0 ]; then
|
|
215
|
+
[ -s "$tmp_dir/lint.log" ] && cat "$tmp_dir/lint.log"
|
|
216
|
+
printf "${RED}✗ Failed with exit code %d${NC}\n" $lint_status
|
|
217
|
+
else
|
|
218
|
+
printf "${GREEN}✓ Passed${NC}\n"
|
|
219
|
+
fi
|
|
147
220
|
|
|
148
221
|
local format_label="Oxfmt Check"
|
|
149
222
|
[ "$FIX_MODE" = true ] && format_label="Oxfmt Format"
|
|
150
223
|
printf "\n${CYAN_BG}${BRIGHT_WHITE} RUN ${NC} ${format_label}\n\n"
|
|
151
|
-
[ -
|
|
152
|
-
|
|
224
|
+
if [ $format_status -ne 0 ]; then
|
|
225
|
+
[ -s "$tmp_dir/format.log" ] && cat "$tmp_dir/format.log"
|
|
226
|
+
printf "${RED}✗ Failed with exit code %d${NC}\n" $format_status
|
|
227
|
+
else
|
|
228
|
+
printf "${GREEN}✓ Passed${NC}\n"
|
|
229
|
+
fi
|
|
153
230
|
|
|
154
231
|
if [ "$FIX_MODE" = false ]; then
|
|
155
232
|
printf "\n${CYAN_BG}${BRIGHT_WHITE} RUN ${NC} Knip (unused code)\n\n"
|
|
156
|
-
[ -
|
|
157
|
-
|
|
233
|
+
if [ $knip_status -ne 0 ]; then
|
|
234
|
+
[ -s "$tmp_dir/knip.log" ] && cat "$tmp_dir/knip.log"
|
|
235
|
+
printf "${RED}✗ Failed with exit code %d${NC}\n" $knip_status
|
|
236
|
+
else
|
|
237
|
+
printf "${GREEN}✓ Passed${NC}\n"
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
if [ -n "$checker_pid" ]; then
|
|
241
|
+
printf "\n${CYAN_BG}${BRIGHT_WHITE} RUN ${NC} Test Conventions (@jterrazz/test)\n\n"
|
|
242
|
+
if [ $checker_status -ne 0 ]; then
|
|
243
|
+
[ -s "$tmp_dir/checker.log" ] && cat "$tmp_dir/checker.log"
|
|
244
|
+
printf "${RED}✗ Failed with exit code %d${NC}\n" $checker_status
|
|
245
|
+
else
|
|
246
|
+
printf "${GREEN}✓ Passed${NC}\n"
|
|
247
|
+
fi
|
|
248
|
+
fi
|
|
158
249
|
fi
|
|
159
250
|
|
|
160
251
|
# Summary
|
|
@@ -164,7 +255,7 @@ run_checks() {
|
|
|
164
255
|
printf "\n${CYAN_BG}${BRIGHT_WHITE} END ${NC} Finalizing quality checks\n\n"
|
|
165
256
|
fi
|
|
166
257
|
|
|
167
|
-
if [ $type_status -eq 0 ] && [ $lint_status -eq 0 ] && [ $format_status -eq 0 ] && [ $knip_status -eq 0 ]; then
|
|
258
|
+
if [ $type_status -eq 0 ] && [ $lint_status -eq 0 ] && [ $format_status -eq 0 ] && [ $knip_status -eq 0 ] && [ $checker_status -eq 0 ]; then
|
|
168
259
|
printf "${GREEN}✓ All checks passed${NC}\n"
|
|
169
260
|
exit 0
|
|
170
261
|
else
|
package/lib/merge-knip-config.js
CHANGED
|
@@ -75,7 +75,7 @@ if (existsSync('docs')) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// Scan for fixtures/** and expected/** directories anywhere in the tree (up to 3 levels)
|
|
78
|
-
const scanDirs = ['tests', 'test', 'src'];
|
|
78
|
+
const scanDirs = ['specs', 'tests', 'test', 'src'];
|
|
79
79
|
for (const root of scanDirs) {
|
|
80
80
|
if (!existsSync(root)) {
|
|
81
81
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jterrazz/typescript",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"author": "Jean-Baptiste Terrazzoni <contact@jterrazz.com>",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"types": "./src/index.d.ts",
|
|
24
24
|
"default": "./src/index.js"
|
|
25
25
|
},
|
|
26
|
+
"./oxlint": {
|
|
27
|
+
"types": "./src/oxlint.d.ts",
|
|
28
|
+
"default": "./src/oxlint.js"
|
|
29
|
+
},
|
|
26
30
|
"./tsconfig/*": "./presets/tsconfig/*.json",
|
|
27
31
|
"./tsconfig/*.json": "./presets/tsconfig/*.json",
|
|
28
32
|
"./tsdown/*": "./presets/tsdown/*.js",
|
|
@@ -49,12 +53,20 @@
|
|
|
49
53
|
"tsdown": "^0.22.9",
|
|
50
54
|
"typedoc": "^0.28.20",
|
|
51
55
|
"typedoc-plugin-markdown": "^4.12.0",
|
|
52
|
-
"typescript": "^6.0.0"
|
|
53
|
-
"typescript-go": "npm:typescript@^7.0.2"
|
|
56
|
+
"typescript": "^6.0.0"
|
|
54
57
|
},
|
|
55
58
|
"devDependencies": {
|
|
56
|
-
"@jterrazz/test": "^
|
|
59
|
+
"@jterrazz/test": "^9.0.0",
|
|
57
60
|
"@types/node": "^26.1.1",
|
|
58
61
|
"vitest": "^4.1.10"
|
|
62
|
+
},
|
|
63
|
+
"optionalDependencies": {
|
|
64
|
+
"@typescript/typescript-darwin-arm64": "^7.0.2",
|
|
65
|
+
"@typescript/typescript-darwin-x64": "^7.0.2",
|
|
66
|
+
"@typescript/typescript-linux-arm": "^7.0.2",
|
|
67
|
+
"@typescript/typescript-linux-arm64": "^7.0.2",
|
|
68
|
+
"@typescript/typescript-linux-x64": "^7.0.2",
|
|
69
|
+
"@typescript/typescript-win32-arm64": "^7.0.2",
|
|
70
|
+
"@typescript/typescript-win32-x64": "^7.0.2"
|
|
59
71
|
}
|
|
60
72
|
}
|
package/presets/oxlint/base.js
CHANGED
|
@@ -4,6 +4,19 @@ import { defineConfig } from 'oxlint';
|
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
const perfectionistPath = require.resolve('eslint-plugin-perfectionist');
|
|
6
6
|
|
|
7
|
+
/*
|
|
8
|
+
* Base lint preset — tooling rules only. Wiring is EXPLICIT: this preset never
|
|
9
|
+
* auto-detects other jterrazz packages. A project using @jterrazz/test composes
|
|
10
|
+
* its `testing` fragment itself:
|
|
11
|
+
*
|
|
12
|
+
* import { testing } from '@jterrazz/test/oxlint';
|
|
13
|
+
* import { compose, node } from '@jterrazz/typescript/oxlint';
|
|
14
|
+
*
|
|
15
|
+
* export default compose(node, testing);
|
|
16
|
+
*
|
|
17
|
+
* (The @jterrazz/test plugin is ESM-only: a CommonJS oxlint config silently
|
|
18
|
+
* drops it — `typescript check` still warns loudly about that pitfall.)
|
|
19
|
+
*/
|
|
7
20
|
export default defineConfig({
|
|
8
21
|
plugins: ['typescript', 'import', 'oxc', 'unicorn'],
|
|
9
22
|
jsPlugins: [perfectionistPath],
|
package/src/oxlint.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type ConfigObject = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
declare const expo: ConfigObject;
|
|
4
|
+
declare const hexagonal: ConfigObject;
|
|
5
|
+
declare const next: ConfigObject;
|
|
6
|
+
declare const node: ConfigObject;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Deterministic merge of oxlint config fragments, left to right:
|
|
10
|
+
* jsPlugins/plugins/ignorePatterns/extends concatenated + deduped,
|
|
11
|
+
* rules/categories shallow-merged (last wins), overrides concatenated.
|
|
12
|
+
*/
|
|
13
|
+
declare function compose(...fragments: ConfigObject[]): ConfigObject;
|
|
14
|
+
|
|
15
|
+
export { compose, expo, hexagonal, next, node };
|
package/src/oxlint.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The tool-facing oxlint entry (`@jterrazz/typescript/oxlint`): the named
|
|
3
|
+
* presets plus the `compose()` helper. Wiring is EXPLICIT — a consumer
|
|
4
|
+
* composes exactly the fragments it wants, nothing is auto-detected:
|
|
5
|
+
*
|
|
6
|
+
* import { testing } from '@jterrazz/test/oxlint';
|
|
7
|
+
* import { compose, node } from '@jterrazz/typescript/oxlint';
|
|
8
|
+
*
|
|
9
|
+
* export default compose(node, testing);
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Config keys concatenated across fragments, duplicates dropped (===). */
|
|
13
|
+
const CONCAT_DEDUPE = new Set(['extends', 'ignorePatterns', 'jsPlugins', 'plugins']);
|
|
14
|
+
/** Config keys concatenated verbatim (order matters, no dedupe). */
|
|
15
|
+
const CONCAT = new Set(['overrides']);
|
|
16
|
+
/** Config keys shallow-merged as objects — the LAST fragment wins per key. */
|
|
17
|
+
const SHALLOW_MERGE = new Set(['categories', 'env', 'globals', 'rules', 'settings']);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Deterministic merge of oxlint config fragments, left to right:
|
|
21
|
+
* `jsPlugins` / `plugins` / `ignorePatterns` / `extends` are concatenated and
|
|
22
|
+
* deduped, `rules` / `categories` (and env/globals/settings) are shallow-merged
|
|
23
|
+
* with last-wins per key, `overrides` are concatenated, and any other key is
|
|
24
|
+
* taken from the last fragment that sets it.
|
|
25
|
+
*/
|
|
26
|
+
export function compose(...fragments) {
|
|
27
|
+
const merged = {};
|
|
28
|
+
for (const fragment of fragments) {
|
|
29
|
+
if (!fragment || typeof fragment !== 'object') {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
for (const [key, value] of Object.entries(fragment)) {
|
|
33
|
+
if (value === undefined) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (CONCAT_DEDUPE.has(key)) {
|
|
37
|
+
const previous = Array.isArray(merged[key]) ? merged[key] : [];
|
|
38
|
+
const combined = [...previous, ...(Array.isArray(value) ? value : [value])];
|
|
39
|
+
merged[key] = combined.filter((entry, index) => combined.indexOf(entry) === index);
|
|
40
|
+
} else if (CONCAT.has(key)) {
|
|
41
|
+
const previous = Array.isArray(merged[key]) ? merged[key] : [];
|
|
42
|
+
merged[key] = [...previous, ...(Array.isArray(value) ? value : [value])];
|
|
43
|
+
} else if (SHALLOW_MERGE.has(key)) {
|
|
44
|
+
merged[key] = { ...merged[key], ...value };
|
|
45
|
+
} else {
|
|
46
|
+
merged[key] = value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return merged;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { default as hexagonal } from '../presets/oxlint/architectures/hexagonal.js';
|
|
54
|
+
export { default as expo } from '../presets/oxlint/expo.js';
|
|
55
|
+
export { default as next } from '../presets/oxlint/next.js';
|
|
56
|
+
export { default as node } from '../presets/oxlint/node.js';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { compose, expo, hexagonal, next, node } from './oxlint.js';
|
|
4
|
+
|
|
5
|
+
test('concatenates and dedupes plugin lists', () => {
|
|
6
|
+
// Given - two fragments sharing one jsPlugin
|
|
7
|
+
const merged = compose(
|
|
8
|
+
{ jsPlugins: ['a', 'b'], plugins: ['typescript'] },
|
|
9
|
+
{ jsPlugins: ['b', 'c'], plugins: ['typescript', 'import'] },
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
// Then - order preserved, duplicates dropped
|
|
13
|
+
expect(merged.jsPlugins).toEqual(['a', 'b', 'c']);
|
|
14
|
+
expect(merged.plugins).toEqual(['typescript', 'import']);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('shallow-merges rules with last fragment winning', () => {
|
|
18
|
+
// Given - two fragments disagreeing on one rule
|
|
19
|
+
const merged = compose(
|
|
20
|
+
{ rules: { curly: 'error', 'no-ternary': 'off' } },
|
|
21
|
+
{ rules: { curly: 'off', 'jterrazz/b4-given-then': 'error' } },
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// Then - the later fragment wins per key, others survive
|
|
25
|
+
expect(merged.rules).toEqual({
|
|
26
|
+
curly: 'off',
|
|
27
|
+
'jterrazz/b4-given-then': 'error',
|
|
28
|
+
'no-ternary': 'off',
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('concatenates overrides in order without deduping', () => {
|
|
33
|
+
// Given - two fragments each shipping an override
|
|
34
|
+
const first = { files: ['**/*.specification.ts'], rules: {} };
|
|
35
|
+
const second = { files: ['src/**'], rules: {} };
|
|
36
|
+
const merged = compose({ overrides: [first] }, { overrides: [second] });
|
|
37
|
+
|
|
38
|
+
// Then - both overrides survive, in composition order
|
|
39
|
+
expect(merged.overrides).toEqual([first, second]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('concatenates and dedupes ignorePatterns', () => {
|
|
43
|
+
// Given - overlapping ignore lists
|
|
44
|
+
const merged = compose(
|
|
45
|
+
{ ignorePatterns: ['dist/**', 'node_modules/**'] },
|
|
46
|
+
{ ignorePatterns: ['node_modules/**', '**/fixtures/**'] },
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Then - one entry each
|
|
50
|
+
expect(merged.ignorePatterns).toEqual(['dist/**', 'node_modules/**', '**/fixtures/**']);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('takes unknown scalar keys from the last fragment', () => {
|
|
54
|
+
// Given - fragments disagreeing on a scalar key
|
|
55
|
+
const merged = compose({ somethingElse: 1 }, { somethingElse: 2 });
|
|
56
|
+
|
|
57
|
+
// Then - last wins
|
|
58
|
+
expect(merged.somethingElse).toBe(2);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('ignores null and undefined fragments', () => {
|
|
62
|
+
// Given - a composition with holes (conditional fragments)
|
|
63
|
+
const merged = compose(
|
|
64
|
+
undefined as unknown as Record<string, unknown>,
|
|
65
|
+
{ rules: { curly: 'error' } },
|
|
66
|
+
null as unknown as Record<string, unknown>,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Then - the holes contribute nothing
|
|
70
|
+
expect(merged.rules).toEqual({ curly: 'error' });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('exports the named presets', () => {
|
|
74
|
+
// Given - the tool-facing entry
|
|
75
|
+
// Then - every preset is a config object
|
|
76
|
+
for (const preset of [node, expo, next, hexagonal]) {
|
|
77
|
+
expect(typeof preset).toBe('object');
|
|
78
|
+
}
|
|
79
|
+
});
|