add traverse test

This commit is contained in:
Adam Hathcock
2025-04-10 14:30:12 +01:00
parent 7f2614cc00
commit 5f35d1fc9b
4 changed files with 184 additions and 42 deletions
@@ -0,0 +1,45 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Traverser > root and two children with referenceId 1`] = `
{
"applicationId": "1",
"arr": null,
"attachedProp": null,
"crazyProp": null,
"detachedProp": null,
"detachedProp2": null,
"dynamicProp": 123,
"id": "efeadaca70a85ae6d3acfc93a8b380db",
"list": [
{
"applicationId": null,
"data": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
],
"id": "0e61e61edee00404ec6e0f9f594bce24",
"speckle_type": "Speckle.Core.Models.DataChunk",
},
],
"list2": [
{
"applicationId": null,
"data": [
1,
10,
],
"id": "f70738e3e3e593ac11099a6ed6b71154",
"speckle_type": "Speckle.Core.Models.DataChunk",
},
],
"speckle_type": "Speckle.Core.Tests.Unit.Models.BaseTests+SampleObjectBase2",
}
`;
@@ -0,0 +1,58 @@
import { describe, expect, test } from 'vitest'
import { Base } from '../types/types.js'
import ObjectLoader2 from './objectLoader2.js'
import Traverser from './traverser.js'
describe('Traverser', () => {
test('root and two children with referenceId', async () => {
const root = `{
"list": [{
"speckle_type": "reference",
"referencedId": "0e61e61edee00404ec6e0f9f594bce24",
"__closure": null
}],
"list2": [{
"speckle_type": "reference",
"referencedId": "f70738e3e3e593ac11099a6ed6b71154",
"__closure": null
}],
"arr": null,
"detachedProp": null,
"detachedProp2": null,
"attachedProp": null,
"crazyProp": null,
"applicationId": "1",
"speckle_type": "Speckle.Core.Tests.Unit.Models.BaseTests+SampleObjectBase2",
"dynamicProp": 123,
"id": "efeadaca70a85ae6d3acfc93a8b380db",
"__closure": {
"0e61e61edee00404ec6e0f9f594bce24": 100,
"f70738e3e3e593ac11099a6ed6b71154": 100
}
}`
const list1 = `{
"data": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
"applicationId": null,
"speckle_type": "Speckle.Core.Models.DataChunk",
"id": "0e61e61edee00404ec6e0f9f594bce24"
}`
const list2 = `{
"data": [1.0, 10.0],
"applicationId": null,
"speckle_type": "Speckle.Core.Models.DataChunk",
"id": "f70738e3e3e593ac11099a6ed6b71154"
}`
const rootObj = JSON.parse(root) as Base
const list1Obj = JSON.parse(list1) as Base
const list2Obj = JSON.parse(list2) as Base
const loader = ObjectLoader2.createFromObjects([rootObj, list1Obj, list2Obj])
const traverser = new Traverser(loader)
const r = await traverser.traverse()
expect(r).toMatchSnapshot()
})
})
@@ -1,4 +1,4 @@
import { Base, DataChunk, isBase } from '../types/types.js'
import { Base, DataChunk, isBase, isReference, isScalar } from '../types/types.js'
import ObjectLoader2 from './objectLoader2.js'
export type ProgressStage = 'download' | 'construction'
@@ -24,38 +24,43 @@ export default class Traverser {
this.#loader = loader
}
async getAndConstructObject(onProgress: OnProgress) {
let firstObjectPromise: Promise<void> | undefined = undefined
let first = true
async traverse(onProgress?: OnProgress): Promise<Base> {
let firstObjectPromise: Promise<Base> | undefined = undefined
for await (const obj of this.#loader.getObjectIterator()) {
if (first) {
if (!firstObjectPromise) {
firstObjectPromise = this.traverseBase(obj, onProgress)
first = false
}
}
if (firstObjectPromise) {
await firstObjectPromise
return await firstObjectPromise
} else {
throw new Error('No objects found')
}
}
async traverseArray(obj: Array<unknown>, onProgress: OnProgress): Promise<void> {
const promises: Promise<void>[] = []
for (const arrayItem of obj) {
if (isBase(arrayItem)) {
promises.push(this.traverseBase(arrayItem, onProgress))
async traverseArray(array: Array<unknown>, onProgress?: OnProgress): Promise<void> {
for (let i = 0; i < 10; i++) {
const prop = array[i]
if (isScalar(prop)) continue
if (isBase(prop)) {
array[i] = await this.traverseBase(prop, onProgress)
} else if (isReference(prop)) {
array[i] = await this.traverseBase(
await this.#loader.getObject({ id: prop.referencedId }),
onProgress
)
}
}
await Promise.all(promises)
}
async traverseBase(obj: Base, onProgress: OnProgress): Promise<void> {
async traverseBase(base: Base, onProgress?: OnProgress): Promise<Base> {
for (const ignoredProp of this.#options.excludeProps || []) {
delete (obj as never)[ignoredProp]
delete (base as never)[ignoredProp]
}
if (obj.__closure) {
const ids = Object.keys(obj.__closure)
const promises: Promise<void>[] = []
if (base.__closure) {
const ids = Object.keys(base.__closure)
const promises: Promise<Base>[] = []
for (const id of ids) {
promises.push(
this.traverseBase(await this.#loader.getObject({ id }), onProgress)
@@ -63,40 +68,45 @@ export default class Traverser {
}
await Promise.all(promises)
}
if (obj.referenceId) {
await this.traverseBase(
await this.#loader.getObject({ id: obj.referenceId }),
onProgress
)
}
delete (base as never)['__closure']
// De-chunk
if (obj.speckle_type?.includes('DataChunk')) {
const chunk = obj as DataChunk
if (base.speckle_type?.includes('DataChunk')) {
const chunk = base as DataChunk
if (chunk.data) {
await this.traverseArray(chunk.data, onProgress)
}
}
//other props
for (const prop in obj) {
for (const prop in base) {
if (prop === '__closure') continue
if (prop === 'referenceId') continue
if (prop === 'speckle_type') continue
const objProp = (obj as unknown as Record<string, unknown>)[prop]
if (isBase(objProp)) {
await this.traverseBase(objProp, onProgress)
}
if (Array.isArray(objProp)) {
await this.traverseArray(objProp, onProgress)
if (prop === 'data') continue
const baseProp = (base as unknown as Record<string, unknown>)[prop]
if (isScalar(baseProp)) continue
if (isBase(baseProp)) {
await this.traverseBase(baseProp, onProgress)
} else if (isReference(baseProp)) {
await this.traverseBase(
await this.#loader.getObject({ id: baseProp.referencedId }),
onProgress
)
} else if (Array.isArray(baseProp)) {
await this.traverseArray(baseProp, onProgress)
}
}
onProgress({
stage: 'construction',
current:
++this.#traversedReferencesCount > this.#totalChildrenCount
? this.#totalChildrenCount
: this.#traversedReferencesCount,
total: this.#totalChildrenCount
})
if (onProgress) {
onProgress({
stage: 'construction',
current:
++this.#traversedReferencesCount > this.#totalChildrenCount
? this.#totalChildrenCount
: this.#traversedReferencesCount,
total: this.#totalChildrenCount
})
}
return base
}
}
+30 -1
View File
@@ -13,7 +13,12 @@ export interface Item {
export interface Base {
id: string
speckle_type: string
referenceId?: string
__closure?: Record<string, number>
}
export interface Reference {
speckle_type: string
referencedId: string
__closure?: Record<string, number>
}
@@ -29,3 +34,27 @@ export function isBase(maybeBase?: unknown): maybeBase is Base {
typeof maybeBase.id === 'string'
)
}
export function isReference(maybeRef?: unknown): maybeRef is Reference {
return (
maybeRef !== null &&
typeof maybeRef === 'object' &&
'referencedId' in maybeRef &&
typeof maybeRef.referencedId === 'string'
)
}
export function isScalar(
value: unknown
): value is string | number | boolean | bigint | symbol | undefined {
const type = typeof value
return (
value === null ||
type === 'string' ||
type === 'number' ||
type === 'boolean' ||
type === 'bigint' ||
type === 'symbol' ||
type === 'undefined'
)
}