@treelocator/runtime 0.4.6 → 0.4.7
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/dist/functions/formatAncestryChain.d.ts +3 -2
- package/dist/functions/formatAncestryChain.js +21 -5
- package/dist/functions/formatAncestryChain.test.js +58 -0
- package/package.json +1 -1
- package/src/functions/formatAncestryChain.test.ts +28 -0
- package/src/functions/formatAncestryChain.ts +21 -5
|
@@ -20,8 +20,9 @@ export interface AncestryItem {
|
|
|
20
20
|
export declare function collectAncestry(node: TreeNode): AncestryItem[];
|
|
21
21
|
/**
|
|
22
22
|
* Truncate ancestry to keep only the local context.
|
|
23
|
-
* - If the clicked element has no
|
|
24
|
-
* - If the clicked element has a
|
|
23
|
+
* - If the clicked element has no file: keep up to the first ancestor with a file.
|
|
24
|
+
* - If the clicked element has a file: keep up to the first ancestor with a DIFFERENT file.
|
|
25
|
+
* Checks both client filePath and serverComponents for file info.
|
|
25
26
|
* The ancestry array is bottom-up: index 0 = clicked element, last = root.
|
|
26
27
|
*/
|
|
27
28
|
export declare function truncateAtFirstFile(items: AncestryItem[]): AncestryItem[];
|
|
@@ -122,25 +122,41 @@ function getInnermostNamedComponent(item) {
|
|
|
122
122
|
return undefined;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Get the effective file path from an AncestryItem, checking both
|
|
127
|
+
* client filePath and serverComponents (Next.js RSC, Phoenix, etc.).
|
|
128
|
+
*/
|
|
129
|
+
function getItemFilePath(item) {
|
|
130
|
+
if (item.filePath) return item.filePath;
|
|
131
|
+
if (item.serverComponents && item.serverComponents.length > 0) {
|
|
132
|
+
const comp = item.serverComponents.find(sc => sc.type === "component");
|
|
133
|
+
if (comp) return comp.filePath;
|
|
134
|
+
return item.serverComponents[0].filePath;
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
125
139
|
/**
|
|
126
140
|
* Truncate ancestry to keep only the local context.
|
|
127
|
-
* - If the clicked element has no
|
|
128
|
-
* - If the clicked element has a
|
|
141
|
+
* - If the clicked element has no file: keep up to the first ancestor with a file.
|
|
142
|
+
* - If the clicked element has a file: keep up to the first ancestor with a DIFFERENT file.
|
|
143
|
+
* Checks both client filePath and serverComponents for file info.
|
|
129
144
|
* The ancestry array is bottom-up: index 0 = clicked element, last = root.
|
|
130
145
|
*/
|
|
131
146
|
export function truncateAtFirstFile(items) {
|
|
132
147
|
if (items.length === 0) return items;
|
|
133
|
-
const clickedFile = items[0]
|
|
148
|
+
const clickedFile = getItemFilePath(items[0]);
|
|
134
149
|
if (!clickedFile) {
|
|
135
150
|
// Clicked element has no file: find first ancestor with any file
|
|
136
|
-
const firstWithFile = items.findIndex(item => item
|
|
151
|
+
const firstWithFile = items.findIndex(item => getItemFilePath(item));
|
|
137
152
|
if (firstWithFile === -1) return items;
|
|
138
153
|
return items.slice(0, firstWithFile + 1);
|
|
139
154
|
}
|
|
140
155
|
|
|
141
156
|
// Clicked element has a file: find first ancestor with a different file
|
|
142
157
|
for (let i = 1; i < items.length; i++) {
|
|
143
|
-
|
|
158
|
+
const ancestorFile = getItemFilePath(items[i]);
|
|
159
|
+
if (ancestorFile && ancestorFile !== clickedFile) {
|
|
144
160
|
return items.slice(0, i + 1);
|
|
145
161
|
}
|
|
146
162
|
}
|
|
@@ -367,6 +367,64 @@ describe("formatAncestryChain", () => {
|
|
|
367
367
|
const result = truncateAtFirstFile(items);
|
|
368
368
|
expect(result).toEqual(items);
|
|
369
369
|
});
|
|
370
|
+
it("uses serverComponents file path when filePath is missing", () => {
|
|
371
|
+
const items = [{
|
|
372
|
+
elementName: "div",
|
|
373
|
+
componentName: "TurnActivityBox",
|
|
374
|
+
serverComponents: [{
|
|
375
|
+
name: "TurnActivityBox",
|
|
376
|
+
filePath: "components/MessageRow.tsx",
|
|
377
|
+
line: 921,
|
|
378
|
+
type: "component"
|
|
379
|
+
}]
|
|
380
|
+
}, {
|
|
381
|
+
elementName: "div",
|
|
382
|
+
componentName: "MessageRow",
|
|
383
|
+
serverComponents: [{
|
|
384
|
+
name: "MessageRow",
|
|
385
|
+
filePath: "components/chat/ChatViewport.tsx",
|
|
386
|
+
line: 917,
|
|
387
|
+
type: "component"
|
|
388
|
+
}]
|
|
389
|
+
}, {
|
|
390
|
+
elementName: "main",
|
|
391
|
+
componentName: "Home",
|
|
392
|
+
serverComponents: [{
|
|
393
|
+
name: "Home",
|
|
394
|
+
filePath: "app/page.tsx",
|
|
395
|
+
line: 817,
|
|
396
|
+
type: "component"
|
|
397
|
+
}]
|
|
398
|
+
}];
|
|
399
|
+
const result = truncateAtFirstFile(items);
|
|
400
|
+
expect(result).toEqual([items[0], items[1]]);
|
|
401
|
+
});
|
|
402
|
+
it("uses serverComponents when clicked element has no filePath but has serverComponents", () => {
|
|
403
|
+
const items = [{
|
|
404
|
+
elementName: "span",
|
|
405
|
+
componentName: "Button"
|
|
406
|
+
}, {
|
|
407
|
+
elementName: "div",
|
|
408
|
+
componentName: "Card",
|
|
409
|
+
serverComponents: [{
|
|
410
|
+
name: "Card",
|
|
411
|
+
filePath: "src/Card.tsx",
|
|
412
|
+
line: 10,
|
|
413
|
+
type: "component"
|
|
414
|
+
}]
|
|
415
|
+
}, {
|
|
416
|
+
elementName: "div",
|
|
417
|
+
componentName: "App",
|
|
418
|
+
serverComponents: [{
|
|
419
|
+
name: "App",
|
|
420
|
+
filePath: "src/App.tsx",
|
|
421
|
+
line: 1,
|
|
422
|
+
type: "component"
|
|
423
|
+
}]
|
|
424
|
+
}];
|
|
425
|
+
const result = truncateAtFirstFile(items);
|
|
426
|
+
expect(result).toEqual([items[0], items[1]]);
|
|
427
|
+
});
|
|
370
428
|
it("returns empty array for empty input", () => {
|
|
371
429
|
expect(truncateAtFirstFile([])).toEqual([]);
|
|
372
430
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treelocator/runtime",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "TreeLocatorJS runtime for component ancestry tracking. Alt+click any element to copy its component tree to clipboard. Exposes window.__treelocator__ API for browser automation (Playwright, Puppeteer, Selenium, Cypress).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"locator",
|
|
@@ -336,6 +336,34 @@ describe("formatAncestryChain", () => {
|
|
|
336
336
|
expect(result).toEqual(items);
|
|
337
337
|
});
|
|
338
338
|
|
|
339
|
+
it("uses serverComponents file path when filePath is missing", () => {
|
|
340
|
+
const items: AncestryItem[] = [
|
|
341
|
+
{ elementName: "div", componentName: "TurnActivityBox", serverComponents: [{ name: "TurnActivityBox", filePath: "components/MessageRow.tsx", line: 921, type: "component" }] },
|
|
342
|
+
{ elementName: "div", componentName: "MessageRow", serverComponents: [{ name: "MessageRow", filePath: "components/chat/ChatViewport.tsx", line: 917, type: "component" }] },
|
|
343
|
+
{ elementName: "main", componentName: "Home", serverComponents: [{ name: "Home", filePath: "app/page.tsx", line: 817, type: "component" }] },
|
|
344
|
+
];
|
|
345
|
+
|
|
346
|
+
const result = truncateAtFirstFile(items);
|
|
347
|
+
expect(result).toEqual([
|
|
348
|
+
items[0],
|
|
349
|
+
items[1],
|
|
350
|
+
]);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("uses serverComponents when clicked element has no filePath but has serverComponents", () => {
|
|
354
|
+
const items: AncestryItem[] = [
|
|
355
|
+
{ elementName: "span", componentName: "Button" },
|
|
356
|
+
{ elementName: "div", componentName: "Card", serverComponents: [{ name: "Card", filePath: "src/Card.tsx", line: 10, type: "component" }] },
|
|
357
|
+
{ elementName: "div", componentName: "App", serverComponents: [{ name: "App", filePath: "src/App.tsx", line: 1, type: "component" }] },
|
|
358
|
+
];
|
|
359
|
+
|
|
360
|
+
const result = truncateAtFirstFile(items);
|
|
361
|
+
expect(result).toEqual([
|
|
362
|
+
items[0],
|
|
363
|
+
items[1],
|
|
364
|
+
]);
|
|
365
|
+
});
|
|
366
|
+
|
|
339
367
|
it("returns empty array for empty input", () => {
|
|
340
368
|
expect(truncateAtFirstFile([])).toEqual([]);
|
|
341
369
|
});
|
|
@@ -164,27 +164,43 @@ function getInnermostNamedComponent(item: AncestryItem | null | undefined): stri
|
|
|
164
164
|
return undefined;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Get the effective file path from an AncestryItem, checking both
|
|
169
|
+
* client filePath and serverComponents (Next.js RSC, Phoenix, etc.).
|
|
170
|
+
*/
|
|
171
|
+
function getItemFilePath(item: AncestryItem): string | undefined {
|
|
172
|
+
if (item.filePath) return item.filePath;
|
|
173
|
+
if (item.serverComponents && item.serverComponents.length > 0) {
|
|
174
|
+
const comp = item.serverComponents.find((sc) => sc.type === "component");
|
|
175
|
+
if (comp) return comp.filePath;
|
|
176
|
+
return item.serverComponents[0]!.filePath;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
|
|
167
181
|
/**
|
|
168
182
|
* Truncate ancestry to keep only the local context.
|
|
169
|
-
* - If the clicked element has no
|
|
170
|
-
* - If the clicked element has a
|
|
183
|
+
* - If the clicked element has no file: keep up to the first ancestor with a file.
|
|
184
|
+
* - If the clicked element has a file: keep up to the first ancestor with a DIFFERENT file.
|
|
185
|
+
* Checks both client filePath and serverComponents for file info.
|
|
171
186
|
* The ancestry array is bottom-up: index 0 = clicked element, last = root.
|
|
172
187
|
*/
|
|
173
188
|
export function truncateAtFirstFile(items: AncestryItem[]): AncestryItem[] {
|
|
174
189
|
if (items.length === 0) return items;
|
|
175
190
|
|
|
176
|
-
const clickedFile = items[0]
|
|
191
|
+
const clickedFile = getItemFilePath(items[0]!);
|
|
177
192
|
|
|
178
193
|
if (!clickedFile) {
|
|
179
194
|
// Clicked element has no file: find first ancestor with any file
|
|
180
|
-
const firstWithFile = items.findIndex((item) => item
|
|
195
|
+
const firstWithFile = items.findIndex((item) => getItemFilePath(item));
|
|
181
196
|
if (firstWithFile === -1) return items;
|
|
182
197
|
return items.slice(0, firstWithFile + 1);
|
|
183
198
|
}
|
|
184
199
|
|
|
185
200
|
// Clicked element has a file: find first ancestor with a different file
|
|
186
201
|
for (let i = 1; i < items.length; i++) {
|
|
187
|
-
|
|
202
|
+
const ancestorFile = getItemFilePath(items[i]!);
|
|
203
|
+
if (ancestorFile && ancestorFile !== clickedFile) {
|
|
188
204
|
return items.slice(0, i + 1);
|
|
189
205
|
}
|
|
190
206
|
}
|