mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 17:23:25 +00:00
282 lines
8.0 KiB
TypeScript
282 lines
8.0 KiB
TypeScript
import { readFileSync } from 'node:fs'
|
|
import { createRequire } from 'node:module'
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
type ConfiguratorField = {
|
|
label: string
|
|
path: string
|
|
structure?: ConfiguratorStructure
|
|
type: string
|
|
value: unknown
|
|
}
|
|
|
|
type ConfiguratorStructure = {
|
|
entryDefault?: unknown
|
|
entries?: Array<{ structure: ConfiguratorStructure }>
|
|
fields?: Record<string, ConfiguratorStructure>
|
|
items?: ConfiguratorStructure[]
|
|
keyType?: 'number' | 'string'
|
|
kind: string
|
|
mutableKeys?: boolean
|
|
template?: ConfiguratorStructure
|
|
}
|
|
|
|
type ConfiguratorSection = {
|
|
fields: ConfiguratorField[]
|
|
id: string
|
|
scope: 'config' | 'media'
|
|
}
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const { loadConfiguratorSections } = require('./configurator-fixture.cjs') as {
|
|
loadConfiguratorSections: () => ConfiguratorSection[]
|
|
}
|
|
|
|
const configSource = readFileSync(
|
|
new URL('../../sky_phone/config/config.lua', import.meta.url),
|
|
'utf8',
|
|
)
|
|
|
|
function countStructure(structure: ConfiguratorStructure | undefined): number {
|
|
if (!structure) return 1
|
|
if (structure.fields) {
|
|
return Object.values(structure.fields).reduce(
|
|
(total, field) => total + countStructure(field),
|
|
0,
|
|
)
|
|
}
|
|
if (structure.items) {
|
|
return structure.items.reduce(
|
|
(total, item) => total + countStructure(item),
|
|
0,
|
|
)
|
|
}
|
|
if (structure.entries) {
|
|
return structure.entries.reduce(
|
|
(total, entry) => total + countStructure(entry.structure),
|
|
0,
|
|
)
|
|
}
|
|
return 1
|
|
}
|
|
|
|
describe('admin configurator fixture', () => {
|
|
it('exposes every SQL-managed config.lua root through the full live preview', () => {
|
|
const sections = loadConfiguratorSections()
|
|
const fields = sections.flatMap((section) => section.fields)
|
|
const roots = [
|
|
...configSource.matchAll(/^\s{0,4}Config\.([A-Za-z0-9_]+)\s*=/gm),
|
|
]
|
|
.map((match) => match[1])
|
|
.filter(
|
|
(root) =>
|
|
root !== 'Media' &&
|
|
root !== 'PhoneConfigurator' &&
|
|
root !== 'CommandPermissions' &&
|
|
root !== 'CustomTones',
|
|
)
|
|
|
|
expect(sections).toHaveLength(46)
|
|
expect(
|
|
fields.reduce(
|
|
(total, field) => total + countStructure(field.structure),
|
|
0,
|
|
),
|
|
).toBeGreaterThan(700)
|
|
for (const root of new Set(roots)) {
|
|
expect(
|
|
sections.some(
|
|
(section) =>
|
|
section.id === `config:${root}` ||
|
|
section.fields.some(
|
|
(field) =>
|
|
field.path === root || field.path.startsWith(`${root}.`),
|
|
),
|
|
),
|
|
`Missing Config.${root}`,
|
|
).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('preserves numeric Lua map keys and the false keybind option', () => {
|
|
const fields = loadConfiguratorSections().flatMap(
|
|
(section) => section.fields,
|
|
)
|
|
const darkChat = fields.find((field) => field.path === 'DarkChat')
|
|
const phone = fields.find((field) => field.path === 'Phone')
|
|
const companyJobs = fields.find(
|
|
(field) => field.path === 'Companies.Definitions',
|
|
)
|
|
const garage = fields.find((field) => field.path === 'Garage')
|
|
const timers = (darkChat?.value as Record<string, unknown> | undefined)
|
|
?.AllowedDisappearTimers
|
|
|
|
expect(timers).toMatchObject({
|
|
__skyType: 'map',
|
|
entries: expect.arrayContaining([
|
|
{ key: -1, keyType: 'number', value: true },
|
|
{ key: 0, keyType: 'number', value: true },
|
|
{ key: 604800, keyType: 'number', value: true },
|
|
]),
|
|
})
|
|
expect(darkChat?.structure?.fields?.AllowedDisappearTimers.kind).toBe('map')
|
|
expect(phone?.structure?.fields?.Keybind.kind).toBe('optionalString')
|
|
expect(companyJobs?.label).toBe('Jobs')
|
|
expect(companyJobs?.structure).toMatchObject({
|
|
entryDefault: {
|
|
AcceptsRequests: true,
|
|
Category: 'public_services',
|
|
Job: '',
|
|
ServiceLine: {
|
|
Number: '500',
|
|
Routing: 'round_robin',
|
|
},
|
|
Services: [
|
|
{
|
|
Id: '',
|
|
RequestsEnabled: true,
|
|
Title: '',
|
|
},
|
|
],
|
|
},
|
|
fields: {
|
|
ambulance: { kind: 'table' },
|
|
police: { kind: 'table' },
|
|
},
|
|
kind: 'table',
|
|
mutableKeys: true,
|
|
template: { kind: 'table' },
|
|
})
|
|
expect(
|
|
garage?.structure?.fields?.VehicleImages.fields?.ModelNames.kind,
|
|
).toBe('map')
|
|
})
|
|
|
|
it('exposes the requestable police assistance defaults', () => {
|
|
const companyJobs = loadConfiguratorSections()
|
|
.flatMap((section) => section.fields)
|
|
.find((field) => field.path === 'Companies.Definitions')
|
|
const police = (
|
|
companyJobs?.value as Record<string, Record<string, unknown>> | undefined
|
|
)?.police
|
|
|
|
expect(police).toMatchObject({
|
|
AcceptsRequests: true,
|
|
Emergency: true,
|
|
Services: [
|
|
{
|
|
Id: 'police-assistance',
|
|
RequestsEnabled: true,
|
|
},
|
|
],
|
|
})
|
|
expect(
|
|
companyJobs?.structure?.fields?.police.fields?.Services,
|
|
).toMatchObject({
|
|
items: [
|
|
{
|
|
fields: {
|
|
Id: { kind: 'value', valueType: 'string' },
|
|
RequestsEnabled: { kind: 'value', valueType: 'boolean' },
|
|
},
|
|
kind: 'table',
|
|
},
|
|
],
|
|
kind: 'list',
|
|
template: {
|
|
fields: {
|
|
Id: { kind: 'value', valueType: 'string' },
|
|
RequestsEnabled: { kind: 'value', valueType: 'boolean' },
|
|
},
|
|
kind: 'table',
|
|
},
|
|
})
|
|
})
|
|
|
|
it('allows custom jobs in locked radio channel entries', () => {
|
|
const radio = loadConfiguratorSections()
|
|
.flatMap((section) => section.fields)
|
|
.find((field) => field.path === 'Radio')
|
|
const lockedChannels = radio?.structure?.fields?.LockedChannels
|
|
const jobs = lockedChannels?.items?.[0]?.fields?.jobs
|
|
|
|
expect(jobs).toMatchObject({
|
|
fields: {
|
|
ambulance: { kind: 'value', valueType: 'boolean' },
|
|
police: { kind: 'value', valueType: 'boolean' },
|
|
},
|
|
kind: 'table',
|
|
mutableKeys: true,
|
|
template: { kind: 'value', valueType: 'boolean' },
|
|
})
|
|
expect(lockedChannels?.template?.fields?.jobs).toMatchObject({
|
|
kind: 'table',
|
|
mutableKeys: true,
|
|
template: { kind: 'value', valueType: 'boolean' },
|
|
})
|
|
})
|
|
|
|
it('publishes fixed schemas for every empty configurable collection', () => {
|
|
const fields = loadConfiguratorSections().flatMap(
|
|
(section) => section.fields,
|
|
)
|
|
const root = (path: string) =>
|
|
fields.find((field) => field.path === path)?.structure
|
|
|
|
expect(root('Music')?.fields?.Tracks).toMatchObject({
|
|
items: [],
|
|
kind: 'list',
|
|
template: {
|
|
fields: {
|
|
Artist: { kind: 'value', valueType: 'string' },
|
|
Id: { kind: 'value', valueType: 'string' },
|
|
Title: { kind: 'value', valueType: 'string' },
|
|
},
|
|
kind: 'table',
|
|
},
|
|
})
|
|
expect(root('FlipTok')?.fields?.MusicTracks.template).toMatchObject({
|
|
fields: { Url: { kind: 'value', valueType: 'string' } },
|
|
kind: 'table',
|
|
})
|
|
expect(root('Payphones')?.fields?.CustomLocations.template).toEqual({
|
|
kind: 'vector',
|
|
vectorType: 'vector4',
|
|
})
|
|
expect(root('CrewLink')?.fields?.ExternalPingResources).toMatchObject({
|
|
fields: {},
|
|
kind: 'table',
|
|
mutableKeys: true,
|
|
template: { kind: 'value', valueType: 'boolean' },
|
|
})
|
|
expect(root('CustomApps')?.fields?.TrustedAdapters).toMatchObject({
|
|
fields: {},
|
|
kind: 'table',
|
|
mutableKeys: true,
|
|
template: { kind: 'value', valueType: 'boolean' },
|
|
})
|
|
expect(
|
|
root('Garage')?.fields?.VehicleImages.fields?.ModelNames,
|
|
).toMatchObject({
|
|
entries: [],
|
|
keyType: 'number',
|
|
kind: 'map',
|
|
template: { kind: 'value', valueType: 'string' },
|
|
})
|
|
expect(
|
|
root('Companies.Definitions')?.fields?.ambulance.fields?.Services,
|
|
).toMatchObject({
|
|
items: [],
|
|
kind: 'list',
|
|
template: {
|
|
fields: {
|
|
Id: { kind: 'value', valueType: 'string' },
|
|
RequestsEnabled: { kind: 'value', valueType: 'boolean' },
|
|
},
|
|
kind: 'table',
|
|
},
|
|
})
|
|
})
|
|
})
|