@sorb/seed 0.1.0 → 0.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/LICENSE +201 -0
- package/README.md +15 -5
- package/package.json +6 -3
- package/src/annotateTokens.js +161 -19
- package/src/annotateTokens.test.js +208 -0
- package/src/capture.js +100 -0
- package/src/capture.test.js +142 -0
- package/src/captureCli.js +29 -2
- package/src/cli.js +185 -3
- package/src/cli.test.js +51 -0
- package/src/render/cli.js +82 -0
- package/src/render/cli.test.js +47 -0
- package/src/render/diffCache.js +152 -0
- package/src/render/diffCache.test.js +110 -0
- package/src/render/pagePool.js +82 -0
- package/src/render/pagePool.test.js +23 -0
- package/src/render/tokenInject.js +53 -0
- package/src/render/tokenInject.test.js +59 -0
- package/src/render/walkerBundle.js +24 -0
- package/src/render/walkerBundle.test.js +12 -0
- package/src/render/worker.js +193 -0
- package/src/render/worker.test.js +233 -0
- package/src/variants.js +279 -0
- package/src/variants.test.js +129 -0
package/src/variants.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// sorb-seed — variant lifecycle engine.
|
|
2
|
+
//
|
|
3
|
+
// Provides add and deprecate operations against DTCG component token sources.
|
|
4
|
+
// Each operation mutates the source JSON, bumps the $version, re-runs the SD
|
|
5
|
+
// build, and returns a VariantChangeset describing what changed.
|
|
6
|
+
|
|
7
|
+
import { readFileSync, writeFileSync } from 'fs'
|
|
8
|
+
import { spawn } from 'child_process'
|
|
9
|
+
|
|
10
|
+
// ─── types ───────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} AddVariantOptions
|
|
14
|
+
* @property {string} componentJsonPath - Absolute path to the component DTCG JSON file.
|
|
15
|
+
* @property {string} variantId - Full dot-path of the new variant, e.g. "button.tertiary".
|
|
16
|
+
* @property {string} fromVariant - Full dot-path of the source variant, e.g. "button.primary".
|
|
17
|
+
* @property {string} sdConfigDir - Directory containing sd.config.js (runs `npm run tokens` here).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} DeprecateVariantOptions
|
|
22
|
+
* @property {string} componentJsonPath - Absolute path to the component DTCG JSON file.
|
|
23
|
+
* @property {string} variantId - Full dot-path of the variant to deprecate, e.g. "button.danger".
|
|
24
|
+
* @property {string} replacedBy - Replacement variant dot-path, e.g. "button.error".
|
|
25
|
+
* @property {string} sdConfigDir - Directory containing sd.config.js.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} VariantChangeset
|
|
30
|
+
* @property {'add'|'deprecate'} action - Which operation was performed.
|
|
31
|
+
* @property {string} variantId - The target variant dot-path.
|
|
32
|
+
* @property {string[]} tokenIds - All token ids affected (leaf nodes).
|
|
33
|
+
* @property {string} newVersion - The bumped $version written to the source file.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Bump the semver patch segment of a version string.
|
|
40
|
+
* Non-parseable input returns "1.0.1" as a safe fallback.
|
|
41
|
+
* @param {string} version
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
export const bumpVersion = (version) => {
|
|
45
|
+
if (typeof version !== 'string') return '1.0.1'
|
|
46
|
+
const parts = version.split('.')
|
|
47
|
+
if (parts.length !== 3) return '1.0.1'
|
|
48
|
+
const [major, minor, patch] = parts.map(Number)
|
|
49
|
+
if (parts.some((p, i) => isNaN([major, minor, patch][i]))) return '1.0.1'
|
|
50
|
+
return `${major}.${minor}.${patch + 1}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Walk a variant slice (prop × state tree) and collect fully-qualified token
|
|
55
|
+
* ids for every leaf node (a node that has a `$value` property).
|
|
56
|
+
* @param {string} componentId - e.g. "button"
|
|
57
|
+
* @param {string} variantKey - e.g. "tertiary"
|
|
58
|
+
* @param {Object} sliceObj - The variant's token subtree.
|
|
59
|
+
* @returns {string[]}
|
|
60
|
+
*/
|
|
61
|
+
export const flattenVariantIds = (componentId, variantKey, sliceObj) => {
|
|
62
|
+
const ids = []
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {Object} node
|
|
66
|
+
* @param {string[]} path - segments accumulated so far (below variantKey)
|
|
67
|
+
*/
|
|
68
|
+
const walk = (node, path) => {
|
|
69
|
+
if (node === null || typeof node !== 'object') return
|
|
70
|
+
if ('$value' in node) {
|
|
71
|
+
// Leaf token — build the full dot-path id.
|
|
72
|
+
ids.push([componentId, variantKey, ...path].join('.'))
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
for (const key of Object.keys(node)) {
|
|
76
|
+
if (key.startsWith('$')) continue // skip metadata keys ($type, $description, etc.)
|
|
77
|
+
walk(node[key], [...path, key])
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
walk(sliceObj, [])
|
|
82
|
+
return ids
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Deep-clone a variant slice (the value stored under a variant key).
|
|
87
|
+
* Equivalent to JSON round-trip — suitable for pure token objects.
|
|
88
|
+
* @param {Object} fromSlice
|
|
89
|
+
* @returns {Object}
|
|
90
|
+
*/
|
|
91
|
+
export const cloneVariantSlice = (fromSlice) => JSON.parse(JSON.stringify(fromSlice))
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Walk a variant slice and mark every leaf node as deprecated, setting
|
|
95
|
+
* `$deprecated = true` and `$extensions.sorb.replacedBy` to `replacedBy`.
|
|
96
|
+
* Returns a **deep clone** with the mutations applied; the original is never
|
|
97
|
+
* touched.
|
|
98
|
+
* @param {Object} sliceObj - The variant's token subtree.
|
|
99
|
+
* @param {string} replacedBy - Replacement variant dot-path.
|
|
100
|
+
* @returns {Object} mutated deep clone
|
|
101
|
+
*/
|
|
102
|
+
export const applyDeprecation = (sliceObj, replacedBy) => {
|
|
103
|
+
const clone = cloneVariantSlice(sliceObj)
|
|
104
|
+
|
|
105
|
+
const walk = (node) => {
|
|
106
|
+
if (node === null || typeof node !== 'object') return
|
|
107
|
+
if ('$value' in node) {
|
|
108
|
+
node.$deprecated = true
|
|
109
|
+
node.$extensions = { sorb: { replacedBy } }
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
for (const key of Object.keys(node)) {
|
|
113
|
+
if (key.startsWith('$')) continue
|
|
114
|
+
walk(node[key])
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
walk(clone)
|
|
119
|
+
return clone
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Run `npm run tokens` in the given directory and return a promise that
|
|
124
|
+
* resolves on exit 0 or rejects with the collected output on non-zero exit.
|
|
125
|
+
* @param {string} sdConfigDir
|
|
126
|
+
* @returns {Promise<void>}
|
|
127
|
+
*/
|
|
128
|
+
const runSdBuild = (sdConfigDir) =>
|
|
129
|
+
new Promise((resolve, reject) => {
|
|
130
|
+
const chunks = []
|
|
131
|
+
const child = spawn('npm', ['run', 'tokens'], {
|
|
132
|
+
cwd: sdConfigDir,
|
|
133
|
+
stdio: 'pipe',
|
|
134
|
+
})
|
|
135
|
+
child.stdout.on('data', (d) => chunks.push(d))
|
|
136
|
+
child.stderr.on('data', (d) => chunks.push(d))
|
|
137
|
+
child.on('close', (code) => {
|
|
138
|
+
if (code === 0) {
|
|
139
|
+
resolve()
|
|
140
|
+
} else {
|
|
141
|
+
const output = Buffer.concat(chunks).toString('utf-8')
|
|
142
|
+
reject(
|
|
143
|
+
new Error(
|
|
144
|
+
`SD build failed (exit ${code}) in ${sdConfigDir}:\n${output}`,
|
|
145
|
+
),
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
child.on('error', (err) => reject(err))
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// ─── exported API ─────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Add a new component variant by cloning an existing one.
|
|
156
|
+
*
|
|
157
|
+
* Reads `componentJsonPath`, deep-clones the `fromVariant` slice into a new
|
|
158
|
+
* `variantId` key, bumps the set's `$version`, writes the file back, runs the
|
|
159
|
+
* SD build, and returns a {@link VariantChangeset}.
|
|
160
|
+
*
|
|
161
|
+
* @param {AddVariantOptions} options
|
|
162
|
+
* @returns {Promise<VariantChangeset>}
|
|
163
|
+
*/
|
|
164
|
+
export const addVariant = async (options) => {
|
|
165
|
+
const { componentJsonPath, variantId, fromVariant, sdConfigDir } = options
|
|
166
|
+
|
|
167
|
+
// 1. Read + parse.
|
|
168
|
+
const raw = readFileSync(componentJsonPath, 'utf-8')
|
|
169
|
+
const parsed = JSON.parse(raw)
|
|
170
|
+
|
|
171
|
+
// 2. Resolve keys.
|
|
172
|
+
const componentId = variantId.split('.')[0]
|
|
173
|
+
const newKey = variantId.split('.').slice(1).join('.')
|
|
174
|
+
const fromKey = fromVariant.split('.').slice(1).join('.')
|
|
175
|
+
|
|
176
|
+
// 3. Find component object.
|
|
177
|
+
const componentObj = parsed[componentId]
|
|
178
|
+
if (!componentObj || typeof componentObj !== 'object') {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`addVariant: component "${componentId}" not found in ${componentJsonPath}`,
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 4. Find source slice.
|
|
185
|
+
const fromSlice = componentObj[fromKey]
|
|
186
|
+
if (!fromSlice || typeof fromSlice !== 'object') {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`addVariant: source variant "${fromKey}" not found in component "${componentId}"`,
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 5. Deep-clone into new key.
|
|
193
|
+
componentObj[newKey] = JSON.parse(JSON.stringify(fromSlice))
|
|
194
|
+
|
|
195
|
+
// 6. Bump $version.
|
|
196
|
+
const prevVersion = parsed.$version ?? '1.0.0'
|
|
197
|
+
const newVersion = bumpVersion(String(prevVersion))
|
|
198
|
+
parsed.$version = newVersion
|
|
199
|
+
|
|
200
|
+
// 7. Write back.
|
|
201
|
+
writeFileSync(componentJsonPath, JSON.stringify(parsed, null, 2) + '\n', 'utf-8')
|
|
202
|
+
|
|
203
|
+
// 8. Run SD build.
|
|
204
|
+
await runSdBuild(sdConfigDir)
|
|
205
|
+
|
|
206
|
+
// 9. Collect token ids from the cloned slice.
|
|
207
|
+
const tokenIds = flattenVariantIds(componentId, newKey, componentObj[newKey])
|
|
208
|
+
|
|
209
|
+
return { action: 'add', variantId, tokenIds, newVersion }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Mark every leaf token in a component variant as deprecated.
|
|
214
|
+
*
|
|
215
|
+
* Walks the `variantId` slice, sets `$deprecated = true` and
|
|
216
|
+
* `$extensions.sorb.replacedBy` on every leaf, bumps `$version`, writes the
|
|
217
|
+
* file back, runs the SD build, and returns a {@link VariantChangeset}.
|
|
218
|
+
*
|
|
219
|
+
* @param {DeprecateVariantOptions} options
|
|
220
|
+
* @returns {Promise<VariantChangeset>}
|
|
221
|
+
*/
|
|
222
|
+
export const deprecateVariant = async (options) => {
|
|
223
|
+
const { componentJsonPath, variantId, replacedBy, sdConfigDir } = options
|
|
224
|
+
|
|
225
|
+
// 1. Read + parse.
|
|
226
|
+
const raw = readFileSync(componentJsonPath, 'utf-8')
|
|
227
|
+
const parsed = JSON.parse(raw)
|
|
228
|
+
|
|
229
|
+
// 2. Resolve keys.
|
|
230
|
+
const componentId = variantId.split('.')[0]
|
|
231
|
+
const variantKey = variantId.split('.').slice(1).join('.')
|
|
232
|
+
|
|
233
|
+
// 3. Find variant slice (error if absent).
|
|
234
|
+
const componentObj = parsed[componentId]
|
|
235
|
+
if (!componentObj || typeof componentObj !== 'object') {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`deprecateVariant: component "${componentId}" not found in ${componentJsonPath}`,
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
const variantSlice = componentObj[variantKey]
|
|
241
|
+
if (!variantSlice || typeof variantSlice !== 'object') {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`deprecateVariant: variant "${variantKey}" not found in component "${componentId}"`,
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 4. Walk the slice; mark every leaf node.
|
|
248
|
+
const tokenIds = []
|
|
249
|
+
|
|
250
|
+
const walk = (node, path) => {
|
|
251
|
+
if (node === null || typeof node !== 'object') return
|
|
252
|
+
if ('$value' in node) {
|
|
253
|
+
node.$deprecated = true
|
|
254
|
+
node.$extensions = { sorb: { replacedBy } }
|
|
255
|
+
tokenIds.push([componentId, variantKey, ...path].join('.'))
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
for (const key of Object.keys(node)) {
|
|
259
|
+
if (key.startsWith('$')) continue
|
|
260
|
+
walk(node[key], [...path, key])
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
walk(variantSlice, [])
|
|
265
|
+
|
|
266
|
+
// 5. Bump $version.
|
|
267
|
+
const prevVersion = parsed.$version ?? '1.0.0'
|
|
268
|
+
const newVersion = bumpVersion(String(prevVersion))
|
|
269
|
+
parsed.$version = newVersion
|
|
270
|
+
|
|
271
|
+
// 6. Write back.
|
|
272
|
+
writeFileSync(componentJsonPath, JSON.stringify(parsed, null, 2) + '\n', 'utf-8')
|
|
273
|
+
|
|
274
|
+
// 7. Run SD build.
|
|
275
|
+
await runSdBuild(sdConfigDir)
|
|
276
|
+
|
|
277
|
+
// 8. Return changeset.
|
|
278
|
+
return { action: 'deprecate', variantId, tokenIds, newVersion }
|
|
279
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Tests for the variants.js pure helpers.
|
|
2
|
+
// Run: node --test src/variants.test.js
|
|
3
|
+
import { test } from 'node:test'
|
|
4
|
+
import assert from 'node:assert/strict'
|
|
5
|
+
import {
|
|
6
|
+
bumpVersion,
|
|
7
|
+
flattenVariantIds,
|
|
8
|
+
cloneVariantSlice,
|
|
9
|
+
applyDeprecation,
|
|
10
|
+
} from './variants.js'
|
|
11
|
+
|
|
12
|
+
// ─── In-memory button component token matrix ─────────────────────────────────
|
|
13
|
+
|
|
14
|
+
const BUTTON_PRIMARY_SLICE = {
|
|
15
|
+
bg: {
|
|
16
|
+
default: { $value: '#0f65ef', $type: 'color' },
|
|
17
|
+
hover: { $value: '#0a4fc4', $type: 'color' },
|
|
18
|
+
},
|
|
19
|
+
text: {
|
|
20
|
+
default: { $value: '#ffffff', $type: 'color' },
|
|
21
|
+
},
|
|
22
|
+
border: {
|
|
23
|
+
default: { $value: '#0f65ef', $type: 'color' },
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const BUTTON_SINGLE_LEVEL_SLICE = {
|
|
28
|
+
radius: { $value: '4px', $type: 'dimension' },
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── flattenVariantIds ────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
test('flattenVariantIds — button.primary slice produces expected ids', () => {
|
|
34
|
+
const ids = flattenVariantIds('button', 'primary', BUTTON_PRIMARY_SLICE)
|
|
35
|
+
assert.deepEqual(ids.sort(), [
|
|
36
|
+
'button.primary.bg.default',
|
|
37
|
+
'button.primary.bg.hover',
|
|
38
|
+
'button.primary.border.default',
|
|
39
|
+
'button.primary.text.default',
|
|
40
|
+
])
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('flattenVariantIds — single-level slice produces correct ids', () => {
|
|
44
|
+
const ids = flattenVariantIds('button', 'base', BUTTON_SINGLE_LEVEL_SLICE)
|
|
45
|
+
assert.deepEqual(ids, ['button.base.radius'])
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('flattenVariantIds — skips $-prefixed metadata keys', () => {
|
|
49
|
+
const sliceWithMeta = {
|
|
50
|
+
$description: 'ignored',
|
|
51
|
+
bg: { $value: '#000000', $type: 'color' },
|
|
52
|
+
}
|
|
53
|
+
const ids = flattenVariantIds('button', 'ghost', sliceWithMeta)
|
|
54
|
+
assert.deepEqual(ids, ['button.ghost.bg'])
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// ─── bumpVersion ─────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
test('bumpVersion — "1.0.0" bumps patch to "1.0.1"', () => {
|
|
60
|
+
assert.equal(bumpVersion('1.0.0'), '1.0.1')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('bumpVersion — "2.3.7" bumps patch to "2.3.8"', () => {
|
|
64
|
+
assert.equal(bumpVersion('2.3.7'), '2.3.8')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('bumpVersion — malformed string falls back to "1.0.1"', () => {
|
|
68
|
+
assert.equal(bumpVersion('not-a-semver'), '1.0.1')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('bumpVersion — too few parts falls back to "1.0.1"', () => {
|
|
72
|
+
assert.equal(bumpVersion('1.0'), '1.0.1')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('bumpVersion — non-string input falls back to "1.0.1"', () => {
|
|
76
|
+
assert.equal(bumpVersion(null), '1.0.1')
|
|
77
|
+
assert.equal(bumpVersion(undefined), '1.0.1')
|
|
78
|
+
assert.equal(bumpVersion(42), '1.0.1')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// ─── cloneVariantSlice ────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
test('cloneVariantSlice — returns a deep copy, not the same reference', () => {
|
|
84
|
+
const clone = cloneVariantSlice(BUTTON_PRIMARY_SLICE)
|
|
85
|
+
assert.deepEqual(clone, BUTTON_PRIMARY_SLICE)
|
|
86
|
+
assert.notEqual(clone, BUTTON_PRIMARY_SLICE)
|
|
87
|
+
assert.notEqual(clone.bg, BUTTON_PRIMARY_SLICE.bg)
|
|
88
|
+
assert.notEqual(clone.bg.default, BUTTON_PRIMARY_SLICE.bg.default)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('cloneVariantSlice — mutation of clone does not affect original', () => {
|
|
92
|
+
const clone = cloneVariantSlice(BUTTON_PRIMARY_SLICE)
|
|
93
|
+
clone.bg.default.$value = 'mutated'
|
|
94
|
+
assert.equal(BUTTON_PRIMARY_SLICE.bg.default.$value, '#0f65ef')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// ─── applyDeprecation ─────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
test('applyDeprecation — all leaf nodes get $deprecated:true', () => {
|
|
100
|
+
const result = applyDeprecation(BUTTON_PRIMARY_SLICE, 'button.primary-v2')
|
|
101
|
+
assert.equal(result.bg.default.$deprecated, true)
|
|
102
|
+
assert.equal(result.bg.hover.$deprecated, true)
|
|
103
|
+
assert.equal(result.text.default.$deprecated, true)
|
|
104
|
+
assert.equal(result.border.default.$deprecated, true)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('applyDeprecation — all leaf nodes get $extensions.sorb.replacedBy set', () => {
|
|
108
|
+
const result = applyDeprecation(BUTTON_PRIMARY_SLICE, 'button.primary-v2')
|
|
109
|
+
assert.equal(result.bg.default.$extensions.sorb.replacedBy, 'button.primary-v2')
|
|
110
|
+
assert.equal(result.bg.hover.$extensions.sorb.replacedBy, 'button.primary-v2')
|
|
111
|
+
assert.equal(result.text.default.$extensions.sorb.replacedBy, 'button.primary-v2')
|
|
112
|
+
assert.equal(result.border.default.$extensions.sorb.replacedBy, 'button.primary-v2')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('applyDeprecation — does not mutate the original slice', () => {
|
|
116
|
+
const original = {
|
|
117
|
+
bg: { default: { $value: '#0f65ef', $type: 'color' } },
|
|
118
|
+
}
|
|
119
|
+
applyDeprecation(original, 'button.new')
|
|
120
|
+
assert.equal(original.bg.default.$deprecated, undefined)
|
|
121
|
+
assert.equal(original.bg.default.$extensions, undefined)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('applyDeprecation — skips intermediate (non-leaf) nodes', () => {
|
|
125
|
+
const result = applyDeprecation(BUTTON_PRIMARY_SLICE, 'button.primary-v2')
|
|
126
|
+
// intermediate nodes should not have $deprecated or $extensions set
|
|
127
|
+
assert.equal(result.bg.$deprecated, undefined)
|
|
128
|
+
assert.equal(result.$deprecated, undefined)
|
|
129
|
+
})
|