chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,119 @@
/**
* @vitest-environment node
*
* Converter unit tests for the table query builder. Cover the operator
* round-trips — UI rule → Filter object → UI rule — with attention to the
* valueless `$empty` operator that maps to two distinct UI operators.
*/
import { describe, expect, it } from 'vitest'
import { filterRulesToFilter, filterToRules } from '@/lib/table/query-builder/converters'
import type { FilterRule } from '@/lib/table/types'
function rule(overrides: Partial<FilterRule>): FilterRule {
return {
id: 'rule-1',
logicalOperator: 'and',
column: 'name',
operator: 'eq',
value: '',
...overrides,
}
}
describe('filterRulesToFilter', () => {
it('emits a bare value for eq (containment shorthand)', () => {
expect(filterRulesToFilter([rule({ operator: 'eq', value: 'John' })])).toEqual({ name: 'John' })
})
it('wraps non-eq operators in a $-prefixed operator object', () => {
expect(
filterRulesToFilter([rule({ column: 'email', operator: 'startsWith', value: 'a' })])
).toEqual({ email: { $startsWith: 'a' } })
expect(
filterRulesToFilter([rule({ column: 'email', operator: 'ncontains', value: 'x' })])
).toEqual({ email: { $ncontains: 'x' } })
})
it('parses comma-separated values into arrays for in / nin', () => {
expect(
filterRulesToFilter([rule({ column: 'status', operator: 'nin', value: 'a, b' })])
).toEqual({ status: { $nin: ['a', 'b'] } })
})
it('serializes isEmpty / isNotEmpty to $empty without a value', () => {
expect(filterRulesToFilter([rule({ column: 'phone', operator: 'isEmpty' })])).toEqual({
phone: { $empty: true },
})
expect(filterRulesToFilter([rule({ column: 'phone', operator: 'isNotEmpty' })])).toEqual({
phone: { $empty: false },
})
})
it('merges two AND rules on the same column into one operator object', () => {
const filter = filterRulesToFilter([
rule({ id: 'a', column: 'age', operator: 'gt', value: '18' }),
rule({ id: 'b', column: 'age', operator: 'lt', value: '65' }),
])
expect(filter).toEqual({ age: { $gt: 18, $lt: 65 } })
})
it('normalizes a bare-equality shorthand when merging with an operator', () => {
const filter = filterRulesToFilter([
rule({ id: 'a', column: 'name', operator: 'eq', value: 'John' }),
rule({ id: 'b', column: 'name', operator: 'contains', value: 'oh' }),
])
expect(filter).toEqual({ name: { $eq: 'John', $contains: 'oh' } })
})
it('keeps same-column rules across an OR boundary in separate groups', () => {
const filter = filterRulesToFilter([
rule({ id: 'a', column: 'age', operator: 'gt', value: '18' }),
rule({ id: 'b', logicalOperator: 'or', column: 'age', operator: 'lt', value: '5' }),
])
expect(filter).toEqual({ $or: [{ age: { $gt: 18 } }, { age: { $lt: 5 } }] })
})
})
describe('filterToRules', () => {
it('maps $empty: true back to isEmpty and $empty: false back to isNotEmpty', () => {
const empty = filterToRules({ phone: { $empty: true } })
expect(empty).toHaveLength(1)
expect(empty[0]).toMatchObject({ column: 'phone', operator: 'isEmpty', value: '' })
const notEmpty = filterToRules({ phone: { $empty: false } })
expect(notEmpty[0]).toMatchObject({ column: 'phone', operator: 'isNotEmpty', value: '' })
})
it("treats the string '$empty' operand the same as the boolean (no predicate flip)", () => {
const empty = filterToRules({ phone: { $empty: 'true' } } as unknown as Parameters<
typeof filterToRules
>[0])
expect(empty[0]).toMatchObject({ column: 'phone', operator: 'isEmpty', value: '' })
const notEmpty = filterToRules({ phone: { $empty: 'false' } } as unknown as Parameters<
typeof filterToRules
>[0])
expect(notEmpty[0]).toMatchObject({ column: 'phone', operator: 'isNotEmpty', value: '' })
})
it('round-trips string-pattern operators', () => {
for (const operator of ['contains', 'ncontains', 'startsWith', 'endsWith'] as const) {
const filter = filterRulesToFilter([rule({ column: 'name', operator, value: 'abc' })])
const back = filterToRules(filter)
expect(back[0]).toMatchObject({ column: 'name', operator, value: 'abc' })
}
})
it('round-trips isEmpty through filterRulesToFilter', () => {
const filter = filterRulesToFilter([rule({ column: 'name', operator: 'isEmpty' })])
const back = filterToRules(filter)
expect(back[0]).toMatchObject({ column: 'name', operator: 'isEmpty', value: '' })
})
it('round-trips a multi-operator column (Filter → rules → Filter) without loss', () => {
const original = { age: { $gte: 18, $lte: 65 } }
const rules = filterToRules(original)
expect(rules).toHaveLength(2)
expect(filterRulesToFilter(rules)).toEqual(original)
})
})