import { assertEquals } from "@std/assert"; import { describe, it } from "@std/testing/bdd"; import { EntityId } from "@commontools/runner"; import { isObject, isRecord } from "@commontools/utils/types"; type MockDoc = { get: () => unknown; getEntityId: () => unknown; }; // Create a mock environment for testing reference detection describe("Piece reference detection", () => { // Test the core logic of direct reference finding without maybeGetCellLink it("should find all direct references in an argument structure", () => { // Create mock data with multiple references const mockData = { piece1Id: { "/": "piece-1-id" }, piece2Id: { "/": "piece-2-id" }, }; // Create a value that references two different entity IDs const valueWithMultipleRefs = { firstRef: { cell: mockData.piece1Id, path: [], }, secondRef: { cell: mockData.piece2Id, path: [], }, nestedRefs: { a: { cell: mockData.piece1Id, path: [], }, b: { cell: mockData.piece2Id, path: [], }, }, }; // Track the references we find const foundRefs: EntityId[] = []; // Direct manual detection (not using maybeGetCellLink which requires proper Cell implementation) const findDirectReferences = (value: unknown): void => { if (!value) return; // Check if the value has cell and path properties directly if ( isRecord(value) && value.cell && isRecord(value.cell) && value.path !== undefined ) { const addr = value.cell["/"]; if (typeof addr !== "string" && !(addr instanceof Uint8Array)) { return; } const id = { "/": addr }; if (!foundRefs.some((ref) => ref["/"] === id["/"])) { foundRefs.push(id); } } // Recursively search objects and arrays if (isRecord(value)) { // Check all properties of objects if (!Array.isArray(value)) { for (const key in value) { findDirectReferences(value[key]); } } // Check all items in arrays else { for (const item of value) { findDirectReferences(item); } } } }; // Search for references findDirectReferences(valueWithMultipleRefs); console.log( `Found ${foundRefs.length} direct references:`, foundRefs.map((ref) => ref["/"]).join(", "), ); // We should find both piece1Id and piece2Id assertEquals( foundRefs.length, 2, "Should find exactly 2 unique reference IDs", ); // Verify we found both specific references const foundPiece1 = foundRefs.some((ref) => ref["/"] === mockData.piece1Id["/"] ); const foundPiece2 = foundRefs.some((ref) => ref["/"] === mockData.piece2Id["/"] ); assertEquals(foundPiece1, true, "Should find reference to piece1"); assertEquals(foundPiece2, true, "Should find reference to piece2"); }); // Test specifically the issue where only one reference is found when there are multiple it("should find multiple references in argument data that matches the reported issue", () => { // Mock the scenario where a piece's argument refers to two other pieces const mockPiece1Id = { "/": "piece-1-id" }; const mockPiece2Id = { "/": "piece-2-id" }; // Create a realistic argument cell structure that might be causing the issue // This simulates a more realistic structure based on your description of the problem const argumentData = { // This is a more realistic structure that might be found in an actual argument cell // with references to result cells of other pieces firstPieceRef: { $alias: { cell: mockPiece1Id, path: ["result"], }, }, secondPieceRef: { $alias: { cell: mockPiece2Id, path: ["result"], }, }, // Alternative format - direct references directRefs: { firstPiece: { cell: mockPiece1Id, path: ["result"], }, secondPiece: { cell: mockPiece2Id, path: ["result"], }, }, }; // Let's implement our own reference finding logic to compare with what the system does const findAllReferences = (obj: unknown): EntityId[] => { const refs: EntityId[] = []; const seenIds = new Set(); const traverse = (value: unknown): void => { if (!isRecord(value)) return; // Check for direct cell reference if (value.cell && value.path !== undefined) { // FIXME: types const addr = (value.cell as Record)["/"] as string; if (addr && !seenIds.has(addr)) { refs.push({ "/": addr }); seenIds.add(addr); } } // Check for $alias reference if (isRecord(value.$alias) && isRecord(value.$alias.cell)) { // FIXME: types const addr = value.$alias.cell["/"] as string; if (addr && !seenIds.has(addr)) { refs.push({ "/": addr }); seenIds.add(addr); } } // Traverse objects and arrays if (Array.isArray(value)) { for (const item of value) { traverse(item); } } else { for (const key in value) { traverse(value[key]); } } }; traverse(obj); return refs; }; // Find all references using our custom logic const foundReferences = findAllReferences(argumentData); // We should find both piece references console.log( `Found ${foundReferences.length} references:`, foundReferences.map((ref) => ref["/"]).join(", "), ); assertEquals( foundReferences.length, 2, "Should find both piece references in the argument data", ); // Check that we specifically found both piece IDs const foundPiece1 = foundReferences.some((ref) => ref["/"] === mockPiece1Id["/"] ); const foundPiece2 = foundReferences.some((ref) => ref["/"] === mockPiece2Id["/"] ); assertEquals(foundPiece1, true, "Should find reference to piece1"); assertEquals(foundPiece2, true, "Should find reference to piece2"); // Now let's simulate what happens in the actual getReadingFrom method console.log("Reference structure to examine:"); console.log(JSON.stringify(argumentData, null, 2)); // This will help us see if there might be an ordering issue affecting which // references are found first, potentially causing some to be missed }); // Test for n-depth reference detection it("should follow sourceCell chains to find deeply nested references", () => { // Mock test data const mockPiece1Id = { "/": "piece-1-source" }; const mockPiece2Id = { "/": "piece-2-intermediate" }; const mockPiece3Id = { "/": "piece-3-target" }; // Create a chain of references where: // - piece1 references piece2 via a sourceCell // - piece2 references piece3 via resultRef const piece2WithResultRef = { // This is the intermediate piece data resultRef: { cell: mockPiece3Id, path: [], }, }; // Mock sourceCell with get and getEntityId methods const mockSourceCell = { get: () => piece2WithResultRef, getEntityId: () => mockPiece2Id, }; const piece1WithSourceCell = { // This is the source piece with a sourceCell reference sourceCell: mockSourceCell, }; // Create mock doc with get and getEntityId methods const mockDoc = { get: () => piece1WithSourceCell, getEntityId: () => mockPiece1Id, }; // Test our ability to follow this chain console.log( "Testing n-depth reference detection with sourceCell and resultRef chain...", ); // Simulate the followSourceToResultRef function from the implementation const followSourceToResultRef = ( doc: unknown, visited = new Set(), depth = 0, ): unknown => { if (depth > 10) return undefined; // Prevent infinite recursion // Get the doc ID // FIXME: types const docId = (doc as MockDoc).getEntityId?.(); if (!isObject(docId) || !("/" in docId)) return undefined; // If we've already seen this doc, stop to prevent cycles const docIdStr = typeof docId["/"] === "string" ? docId["/"] : JSON.stringify(docId["/"]); if (visited.has(docIdStr)) return undefined; visited.add(docIdStr); // Get the doc value // FIXME: types const value = (doc as MockDoc).get?.(); // If document has a sourceCell, follow it if (isRecord(value) && value.sourceCell) { return followSourceToResultRef(value.sourceCell, visited, depth + 1); } // If we've reached the end and have a resultRef, return it if (isRecord(value) && isRecord(value.resultRef)) { return value.resultRef.cell; } // Return the document's ID if no further references return docId; }; // Follow the sourceCell chain to find the ultimate reference const ultimateRef = followSourceToResultRef(mockDoc) as Record< string, unknown >; // Verify we found the final target (piece3) assertEquals( ultimateRef["/"], mockPiece3Id["/"], "Should find the final reference through the sourceCell -> resultRef chain", ); }); });