@dfosco/storyboard-react 2.0.0 → 2.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/package.json +2 -2
- package/src/Viewfinder.jsx +47 -214
- package/src/context.jsx +73 -20
- package/src/context.test.jsx +120 -15
- package/src/hooks/useRecord.js +33 -12
- package/src/hooks/useRecord.test.js +92 -1
- package/src/hooks/useScene.js +21 -11
- package/src/hooks/useScene.test.js +40 -13
- package/src/hooks/useSceneData.js +17 -11
- package/src/hooks/useSceneData.test.js +60 -32
- package/src/index.js +3 -1
- package/src/test-utils.js +8 -5
- package/src/vite/data-plugin.js +136 -18
- package/src/vite/data-plugin.test.js +135 -2
package/src/vite/data-plugin.js
CHANGED
|
@@ -1,24 +1,68 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
+
import { execSync } from 'node:child_process'
|
|
3
4
|
import { globSync } from 'glob'
|
|
4
5
|
import { parse as parseJsonc } from 'jsonc-parser'
|
|
5
6
|
|
|
6
7
|
const VIRTUAL_MODULE_ID = 'virtual:storyboard-data-index'
|
|
7
8
|
const RESOLVED_ID = '\0' + VIRTUAL_MODULE_ID
|
|
8
9
|
|
|
9
|
-
const
|
|
10
|
-
const GLOB_PATTERN = '**/*.{scene,object,record}.{json,jsonc}'
|
|
10
|
+
const GLOB_PATTERN = '**/*.{flow,scene,object,record,prototype}.{json,jsonc}'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Extract the data name and type suffix from a file path.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Flows and records inside src/prototypes/{Name}/ get prefixed with the
|
|
15
|
+
* prototype name (e.g. "Dashboard/default"). Objects are never prefixed.
|
|
16
|
+
*
|
|
17
|
+
* e.g. "src/data/default.flow.json" → { name: "default", suffix: "flow" }
|
|
18
|
+
* "src/prototypes/Dashboard/default.flow.json" → { name: "Dashboard/default", suffix: "flow" }
|
|
19
|
+
* "src/prototypes/Dashboard/helpers.object.json"→ { name: "helpers", suffix: "object" }
|
|
16
20
|
*/
|
|
17
21
|
function parseDataFile(filePath) {
|
|
18
22
|
const base = path.basename(filePath)
|
|
19
|
-
const match = base.match(/^(.+)\.(scene|object|record)\.(jsonc?)$/)
|
|
23
|
+
const match = base.match(/^(.+)\.(flow|scene|object|record|prototype)\.(jsonc?)$/)
|
|
20
24
|
if (!match) return null
|
|
21
|
-
|
|
25
|
+
// Normalize .scene → .flow for backward compatibility
|
|
26
|
+
const suffix = match[2] === 'scene' ? 'flow' : match[2]
|
|
27
|
+
let name = match[1]
|
|
28
|
+
|
|
29
|
+
// Prototype metadata files are keyed by their prototype directory name
|
|
30
|
+
if (suffix === 'prototype') {
|
|
31
|
+
const normalized = filePath.replace(/\\/g, '/')
|
|
32
|
+
const protoMatch = normalized.match(/(?:^|\/)src\/prototypes\/([^/]+)\//)
|
|
33
|
+
if (protoMatch) {
|
|
34
|
+
name = protoMatch[1]
|
|
35
|
+
}
|
|
36
|
+
return { name, suffix, ext: match[3] }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Scope flows and records inside src/prototypes/{Name}/ with a prefix
|
|
40
|
+
if (suffix !== 'object') {
|
|
41
|
+
const normalized = filePath.replace(/\\/g, '/')
|
|
42
|
+
const protoMatch = normalized.match(/(?:^|\/)src\/prototypes\/([^/]+)\//)
|
|
43
|
+
if (protoMatch) {
|
|
44
|
+
name = `${protoMatch[1]}/${name}`
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { name, suffix, ext: match[3] }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Look up the git author who first created a file.
|
|
53
|
+
* Used to auto-fill the author field in .prototype.json when missing.
|
|
54
|
+
*/
|
|
55
|
+
function getGitAuthor(root, filePath) {
|
|
56
|
+
try {
|
|
57
|
+
const result = execSync(
|
|
58
|
+
`git log --follow --diff-filter=A --format="%aN" -- "${filePath}"`,
|
|
59
|
+
{ cwd: root, encoding: 'utf-8', timeout: 5000 },
|
|
60
|
+
).trim()
|
|
61
|
+
const lines = result.split('\n').filter(Boolean)
|
|
62
|
+
return lines.length > 0 ? lines[lines.length - 1] : null
|
|
63
|
+
} catch {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
22
66
|
}
|
|
23
67
|
|
|
24
68
|
/**
|
|
@@ -28,7 +72,7 @@ function buildIndex(root) {
|
|
|
28
72
|
const ignore = ['node_modules/**', 'dist/**', '.git/**']
|
|
29
73
|
const files = globSync(GLOB_PATTERN, { cwd: root, ignore, absolute: false })
|
|
30
74
|
|
|
31
|
-
const index = {
|
|
75
|
+
const index = { flow: {}, object: {}, record: {}, prototype: {} }
|
|
32
76
|
const seen = {} // "name.suffix" → absolute path (for duplicate detection)
|
|
33
77
|
|
|
34
78
|
for (const relPath of files) {
|
|
@@ -39,11 +83,17 @@ function buildIndex(root) {
|
|
|
39
83
|
const absPath = path.resolve(root, relPath)
|
|
40
84
|
|
|
41
85
|
if (seen[key]) {
|
|
86
|
+
const hint = parsed.suffix === 'object'
|
|
87
|
+
? ' Objects are globally scoped — even inside src/prototypes/ they share a single namespace.\n' +
|
|
88
|
+
' Rename one of the files to avoid the collision.'
|
|
89
|
+
: ' Flows and records are scoped to their prototype directory.\n' +
|
|
90
|
+
' If both files are global (outside src/prototypes/), rename one to avoid the collision.'
|
|
91
|
+
|
|
42
92
|
throw new Error(
|
|
43
|
-
`[storyboard-data] Duplicate
|
|
93
|
+
`[storyboard-data] Duplicate ${parsed.suffix} "${parsed.name}"\n` +
|
|
44
94
|
` Found at: ${seen[key]}\n` +
|
|
45
95
|
` And at: ${absPath}\n` +
|
|
46
|
-
|
|
96
|
+
hint
|
|
47
97
|
)
|
|
48
98
|
}
|
|
49
99
|
|
|
@@ -77,23 +127,70 @@ function readConfig(root) {
|
|
|
77
127
|
}
|
|
78
128
|
}
|
|
79
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Read modes.config.json from @dfosco/storyboard-core.
|
|
132
|
+
* Returns the full config object { modes, tools }.
|
|
133
|
+
* Falls back to hardcoded defaults if not found.
|
|
134
|
+
*/
|
|
135
|
+
function readModesConfig(root) {
|
|
136
|
+
const fallback = {
|
|
137
|
+
modes: [
|
|
138
|
+
{ name: 'prototype', label: 'Navigate' },
|
|
139
|
+
{ name: 'inspect', label: 'Develop' },
|
|
140
|
+
{ name: 'present', label: 'Collaborate' },
|
|
141
|
+
{ name: 'plan', label: 'Canvas' },
|
|
142
|
+
],
|
|
143
|
+
tools: {},
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Try local workspace path first (monorepo), then node_modules
|
|
147
|
+
const candidates = [
|
|
148
|
+
path.resolve(root, 'packages/core/modes.config.json'),
|
|
149
|
+
path.resolve(root, 'node_modules/@dfosco/storyboard-core/modes.config.json'),
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
for (const filePath of candidates) {
|
|
153
|
+
try {
|
|
154
|
+
const raw = fs.readFileSync(filePath, 'utf-8')
|
|
155
|
+
const parsed = JSON.parse(raw)
|
|
156
|
+
if (Array.isArray(parsed.modes) && parsed.modes.length > 0) {
|
|
157
|
+
return { modes: parsed.modes, tools: parsed.tools ?? {} }
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// try next candidate
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return fallback
|
|
165
|
+
}
|
|
166
|
+
|
|
80
167
|
function generateModule(index, root) {
|
|
81
168
|
const declarations = []
|
|
82
|
-
const
|
|
169
|
+
const INDEX_KEYS = ['flow', 'object', 'record', 'prototype']
|
|
170
|
+
const entries = { flow: [], object: [], record: [], prototype: [] }
|
|
83
171
|
let i = 0
|
|
84
172
|
|
|
85
|
-
for (const suffix of
|
|
173
|
+
for (const suffix of INDEX_KEYS) {
|
|
86
174
|
for (const [name, absPath] of Object.entries(index[suffix])) {
|
|
87
175
|
const varName = `_d${i++}`
|
|
88
176
|
const raw = fs.readFileSync(absPath, 'utf-8')
|
|
89
|
-
|
|
177
|
+
let parsed = parseJsonc(raw)
|
|
178
|
+
|
|
179
|
+
// Auto-fill gitAuthor for prototype metadata from git history
|
|
180
|
+
if (suffix === 'prototype' && parsed && !parsed.gitAuthor) {
|
|
181
|
+
const gitAuthor = getGitAuthor(root, absPath)
|
|
182
|
+
if (gitAuthor) {
|
|
183
|
+
parsed = { ...parsed, gitAuthor }
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
90
187
|
declarations.push(`const ${varName} = ${JSON.stringify(parsed)}`)
|
|
91
188
|
entries[suffix].push(` ${JSON.stringify(name)}: ${varName}`)
|
|
92
189
|
}
|
|
93
190
|
}
|
|
94
191
|
|
|
95
192
|
const imports = [`import { init } from '@dfosco/storyboard-core'`]
|
|
96
|
-
const initCalls = [`init({
|
|
193
|
+
const initCalls = [`init({ flows, objects, records, prototypes })`]
|
|
97
194
|
|
|
98
195
|
// Feature flags from storyboard.config.json
|
|
99
196
|
const { config } = readConfig(root)
|
|
@@ -110,8 +207,25 @@ function generateModule(index, root) {
|
|
|
110
207
|
|
|
111
208
|
// Modes configuration from storyboard.config.json
|
|
112
209
|
if (config?.modes) {
|
|
113
|
-
imports.push(`import { initModesConfig } from '@dfosco/storyboard-core'`)
|
|
210
|
+
imports.push(`import { initModesConfig, registerMode, syncModeClasses, initTools } from '@dfosco/storyboard-core'`)
|
|
114
211
|
initCalls.push(`initModesConfig(${JSON.stringify(config.modes)})`)
|
|
212
|
+
|
|
213
|
+
if (config.modes.enabled) {
|
|
214
|
+
imports.push(`import '@dfosco/storyboard-core/modes.css'`)
|
|
215
|
+
|
|
216
|
+
const modesConfig = readModesConfig(root)
|
|
217
|
+
const modes = config.modes.defaults || modesConfig.modes
|
|
218
|
+
for (const m of modes) {
|
|
219
|
+
initCalls.push(`registerMode(${JSON.stringify(m.name)}, { label: ${JSON.stringify(m.label)} })`)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Seed tool registry from modes.config.json
|
|
223
|
+
if (Object.keys(modesConfig.tools).length > 0) {
|
|
224
|
+
initCalls.push(`initTools(${JSON.stringify(modesConfig.tools)})`)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
initCalls.push(`syncModeClasses()`)
|
|
228
|
+
}
|
|
115
229
|
}
|
|
116
230
|
|
|
117
231
|
return [
|
|
@@ -119,14 +233,18 @@ function generateModule(index, root) {
|
|
|
119
233
|
'',
|
|
120
234
|
declarations.join('\n'),
|
|
121
235
|
'',
|
|
122
|
-
`const
|
|
236
|
+
`const flows = {\n${entries.flow.join(',\n')}\n}`,
|
|
123
237
|
`const objects = {\n${entries.object.join(',\n')}\n}`,
|
|
124
238
|
`const records = {\n${entries.record.join(',\n')}\n}`,
|
|
239
|
+
`const prototypes = {\n${entries.prototype.join(',\n')}\n}`,
|
|
240
|
+
'',
|
|
241
|
+
'// Backward-compatible alias',
|
|
242
|
+
'const scenes = flows',
|
|
125
243
|
'',
|
|
126
244
|
initCalls.join('\n'),
|
|
127
245
|
'',
|
|
128
|
-
`export { scenes, objects, records }`,
|
|
129
|
-
`export const index = { scenes, objects, records }`,
|
|
246
|
+
`export { flows, scenes, objects, records, prototypes }`,
|
|
247
|
+
`export const index = { flows, scenes, objects, records, prototypes }`,
|
|
130
248
|
`export default index`,
|
|
131
249
|
].join('\n')
|
|
132
250
|
}
|
|
@@ -134,7 +252,7 @@ function generateModule(index, root) {
|
|
|
134
252
|
/**
|
|
135
253
|
* Vite plugin for storyboard data discovery.
|
|
136
254
|
*
|
|
137
|
-
* - Scans the repo for *.scene.json, *.object.json, *.record.json
|
|
255
|
+
* - Scans the repo for *.flow.json, *.scene.json (compat), *.object.json, *.record.json
|
|
138
256
|
* - Validates no two files share the same name+suffix (hard build error)
|
|
139
257
|
* - Generates a virtual module `virtual:storyboard-data-index`
|
|
140
258
|
* - Watches for file additions/removals in dev mode
|
|
@@ -69,10 +69,13 @@ describe('storyboardDataPlugin', () => {
|
|
|
69
69
|
const code = plugin.load(RESOLVED_ID)
|
|
70
70
|
|
|
71
71
|
expect(code).toContain("import { init } from '@dfosco/storyboard-core'")
|
|
72
|
-
expect(code).toContain('init({
|
|
72
|
+
expect(code).toContain('init({ flows, objects, records, prototypes })')
|
|
73
73
|
expect(code).toContain('"Test"')
|
|
74
74
|
expect(code).toContain('"Jane"')
|
|
75
75
|
expect(code).toContain('"First"')
|
|
76
|
+
// Backward-compat alias
|
|
77
|
+
expect(code).toContain('const scenes = flows')
|
|
78
|
+
expect(code).toContain('export { flows, scenes, objects, records, prototypes }')
|
|
76
79
|
})
|
|
77
80
|
|
|
78
81
|
it('load returns null for other IDs', () => {
|
|
@@ -93,7 +96,24 @@ describe('storyboardDataPlugin', () => {
|
|
|
93
96
|
)
|
|
94
97
|
|
|
95
98
|
const plugin = createPlugin()
|
|
96
|
-
expect(() => plugin.load(RESOLVED_ID)).toThrow(/Duplicate
|
|
99
|
+
expect(() => plugin.load(RESOLVED_ID)).toThrow(/Duplicate flow "dup"/)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('duplicate objects show globally-scoped hint', () => {
|
|
103
|
+
mkdirSync(path.join(tmpDir, 'src', 'data'), { recursive: true })
|
|
104
|
+
mkdirSync(path.join(tmpDir, 'src', 'prototypes', 'Dashboard'), { recursive: true })
|
|
105
|
+
writeFileSync(
|
|
106
|
+
path.join(tmpDir, 'src', 'data', 'user.object.json'),
|
|
107
|
+
JSON.stringify({ name: 'Global' }),
|
|
108
|
+
)
|
|
109
|
+
writeFileSync(
|
|
110
|
+
path.join(tmpDir, 'src', 'prototypes', 'Dashboard', 'user.object.json'),
|
|
111
|
+
JSON.stringify({ name: 'Local' }),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
const plugin = createPlugin()
|
|
115
|
+
expect(() => plugin.load(RESOLVED_ID)).toThrow(/Duplicate object "user"/)
|
|
116
|
+
expect(() => plugin.load(RESOLVED_ID)).toThrow(/globally scoped/)
|
|
97
117
|
})
|
|
98
118
|
|
|
99
119
|
it('handles JSONC files (with comments)', () => {
|
|
@@ -107,6 +127,19 @@ describe('storyboardDataPlugin', () => {
|
|
|
107
127
|
expect(code).toContain('"JSONC Scene"')
|
|
108
128
|
})
|
|
109
129
|
|
|
130
|
+
it('normalizes .scene files into flow category in the index', () => {
|
|
131
|
+
writeFileSync(
|
|
132
|
+
path.join(tmpDir, 'legacy.scene.json'),
|
|
133
|
+
JSON.stringify({ title: 'Legacy Scene' }),
|
|
134
|
+
)
|
|
135
|
+
const plugin = createPlugin()
|
|
136
|
+
const code = plugin.load(RESOLVED_ID)
|
|
137
|
+
|
|
138
|
+
// .scene.json files should be normalized to the flows category
|
|
139
|
+
expect(code).toContain('"Legacy Scene"')
|
|
140
|
+
expect(code).toContain('init({ flows, objects, records, prototypes })')
|
|
141
|
+
})
|
|
142
|
+
|
|
110
143
|
it('buildStart resets the index cache', () => {
|
|
111
144
|
writeDataFiles(tmpDir)
|
|
112
145
|
const plugin = createPlugin()
|
|
@@ -131,3 +164,103 @@ describe('storyboardDataPlugin', () => {
|
|
|
131
164
|
expect(code3).toContain('"Extra"')
|
|
132
165
|
})
|
|
133
166
|
})
|
|
167
|
+
|
|
168
|
+
describe('prototype scoping', () => {
|
|
169
|
+
it('prefixes flows inside src/prototypes/{Name}/ with the prototype name', () => {
|
|
170
|
+
mkdirSync(path.join(tmpDir, 'src', 'prototypes', 'Dashboard'), { recursive: true })
|
|
171
|
+
writeFileSync(
|
|
172
|
+
path.join(tmpDir, 'src', 'prototypes', 'Dashboard', 'default.flow.json'),
|
|
173
|
+
JSON.stringify({ title: 'Dashboard Default' }),
|
|
174
|
+
)
|
|
175
|
+
writeFileSync(
|
|
176
|
+
path.join(tmpDir, 'src', 'prototypes', 'Dashboard', 'signup.flow.json'),
|
|
177
|
+
JSON.stringify({ title: 'Dashboard Signup' }),
|
|
178
|
+
)
|
|
179
|
+
// Global flow in src/data/
|
|
180
|
+
mkdirSync(path.join(tmpDir, 'src', 'data'), { recursive: true })
|
|
181
|
+
writeFileSync(
|
|
182
|
+
path.join(tmpDir, 'src', 'data', 'default.flow.json'),
|
|
183
|
+
JSON.stringify({ title: 'Global Default' }),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
const plugin = createPlugin()
|
|
187
|
+
const code = plugin.load(RESOLVED_ID)
|
|
188
|
+
|
|
189
|
+
expect(code).toContain('"Dashboard/default"')
|
|
190
|
+
expect(code).toContain('"Dashboard/signup"')
|
|
191
|
+
expect(code).toContain('"default"')
|
|
192
|
+
expect(code).toContain('"Dashboard Default"')
|
|
193
|
+
expect(code).toContain('"Global Default"')
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('prefixes records inside src/prototypes/{Name}/ with the prototype name', () => {
|
|
197
|
+
mkdirSync(path.join(tmpDir, 'src', 'prototypes', 'Blog'), { recursive: true })
|
|
198
|
+
writeFileSync(
|
|
199
|
+
path.join(tmpDir, 'src', 'prototypes', 'Blog', 'posts.record.json'),
|
|
200
|
+
JSON.stringify([{ id: '1', title: 'Scoped Post' }]),
|
|
201
|
+
)
|
|
202
|
+
// Global record
|
|
203
|
+
mkdirSync(path.join(tmpDir, 'src', 'data'), { recursive: true })
|
|
204
|
+
writeFileSync(
|
|
205
|
+
path.join(tmpDir, 'src', 'data', 'posts.record.json'),
|
|
206
|
+
JSON.stringify([{ id: '1', title: 'Global Post' }]),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
const plugin = createPlugin()
|
|
210
|
+
const code = plugin.load(RESOLVED_ID)
|
|
211
|
+
|
|
212
|
+
expect(code).toContain('"Blog/posts"')
|
|
213
|
+
expect(code).toContain('"posts"')
|
|
214
|
+
expect(code).toContain('"Scoped Post"')
|
|
215
|
+
expect(code).toContain('"Global Post"')
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('does NOT prefix objects inside src/prototypes/{Name}/', () => {
|
|
219
|
+
mkdirSync(path.join(tmpDir, 'src', 'prototypes', 'Dashboard'), { recursive: true })
|
|
220
|
+
writeFileSync(
|
|
221
|
+
path.join(tmpDir, 'src', 'prototypes', 'Dashboard', 'helpers.object.json'),
|
|
222
|
+
JSON.stringify({ util: true }),
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
const plugin = createPlugin()
|
|
226
|
+
const code = plugin.load(RESOLVED_ID)
|
|
227
|
+
|
|
228
|
+
// Object should be plain "helpers", NOT "Dashboard/helpers"
|
|
229
|
+
expect(code).toContain('"helpers"')
|
|
230
|
+
expect(code).not.toContain('"Dashboard/helpers"')
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('allows same flow name in different prototypes without clash', () => {
|
|
234
|
+
mkdirSync(path.join(tmpDir, 'src', 'prototypes', 'A'), { recursive: true })
|
|
235
|
+
mkdirSync(path.join(tmpDir, 'src', 'prototypes', 'B'), { recursive: true })
|
|
236
|
+
writeFileSync(
|
|
237
|
+
path.join(tmpDir, 'src', 'prototypes', 'A', 'default.flow.json'),
|
|
238
|
+
JSON.stringify({ from: 'A' }),
|
|
239
|
+
)
|
|
240
|
+
writeFileSync(
|
|
241
|
+
path.join(tmpDir, 'src', 'prototypes', 'B', 'default.flow.json'),
|
|
242
|
+
JSON.stringify({ from: 'B' }),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
const plugin = createPlugin()
|
|
246
|
+
// Should not throw (no duplicate)
|
|
247
|
+
const code = plugin.load(RESOLVED_ID)
|
|
248
|
+
expect(code).toContain('"A/default"')
|
|
249
|
+
expect(code).toContain('"B/default"')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('normalizes .scene.json inside prototypes to scoped flow', () => {
|
|
253
|
+
mkdirSync(path.join(tmpDir, 'src', 'prototypes', 'Legacy'), { recursive: true })
|
|
254
|
+
writeFileSync(
|
|
255
|
+
path.join(tmpDir, 'src', 'prototypes', 'Legacy', 'old.scene.json'),
|
|
256
|
+
JSON.stringify({ compat: true }),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
const plugin = createPlugin()
|
|
260
|
+
const code = plugin.load(RESOLVED_ID)
|
|
261
|
+
|
|
262
|
+
// Should be indexed as a scoped flow, not a scene
|
|
263
|
+
expect(code).toContain('"Legacy/old"')
|
|
264
|
+
expect(code).toContain('flows')
|
|
265
|
+
})
|
|
266
|
+
})
|