TASKS-1823 WebManager module

This commit is contained in:
Igor Martyniuk
2026-08-28 08:35:58 +00:00
parent fdb2cf3837
commit c93d8c025b
654 changed files with 26232 additions and 6075 deletions
@@ -0,0 +1,100 @@
import { test } from '@japa/runner'
import FormSourceBuilder from '#services/module_generator/builders/form_source_builder'
import {
ModuleFieldOption,
ModuleFieldType,
ModuleLayoutConfig,
} from '#services/module_generator/types'
const layout = (type: ModuleFieldType, options: ModuleFieldOption[]): ModuleLayoutConfig => ({
grid: [
{
id: '1',
row: 0,
col: 0,
rowSpan: 1,
colSpan: 1,
isEmpty: false,
mergeTo: null,
body: {
dbColumn: 'category',
type,
label: 'Category',
placeholder: 'Pick one',
required: false,
options,
},
},
],
rows: 1,
cols: 1,
selectedCells: [],
size: 'dialog-default',
})
test.group('FormSourceBuilder grouped options', () => {
test('emits combobox groups bucketed by group name, ungrouped options in an unlabeled group', ({
assert,
}) => {
const source = FormSourceBuilder.buildComponent(
layout(ModuleFieldType.COMBOBOX, [
{ label: 'Apple', value: 'apple', group: 'Fruits' },
{ label: 'Carrot', value: 'carrot', group: 'Veggies' },
{ label: 'Other', value: 'other' },
{ label: 'Pear', value: 'pear', group: 'Fruits' },
])
)
assert.include(
source,
'groups={[{"label":"Fruits","options":[{"label":"Apple","value":"apple"},{"label":"Pear","value":"pear"}]},{"label":"Veggies","options":[{"label":"Carrot","value":"carrot"}]},{"options":[{"label":"Other","value":"other"}]}]}'
)
assert.include(source, 'InputCombobox')
})
test('emits multiselect heading groups when any option has a group', ({ assert }) => {
const source = FormSourceBuilder.buildComponent(
layout(ModuleFieldType.MULTISELECT, [
{ label: 'Apple', value: 'apple', group: 'Fruits' },
{ label: 'Other', value: 'other' },
])
)
assert.include(
source,
'options={[{"heading":"Fruits","options":[{"label":"Apple","value":"apple"}]},{"heading":"","options":[{"label":"Other","value":"other"}]}]}'
)
})
test('keeps multiselect options flat when no groups are set', ({ assert }) => {
const source = FormSourceBuilder.buildComponent(
layout(ModuleFieldType.MULTISELECT, [
{ label: 'Apple', value: 'apple', group: '' },
{ label: 'Other', value: 'other' },
])
)
assert.include(
source,
'options={[{"label":"Apple","value":"apple"},{"label":"Other","value":"other"}]}'
)
})
test('strips group from flat-only fields', ({ assert }) => {
const source = FormSourceBuilder.buildComponent(
layout(ModuleFieldType.SELECT, [{ label: 'Apple', value: 'apple', group: 'Fruits' }])
)
assert.include(source, 'options={[{"label":"Apple","value":"apple"}]}')
assert.notInclude(source, '"group"')
})
test('imports only the composed components present in the emitted JSX', ({ assert }) => {
const source = FormSourceBuilder.buildComponent(layout(ModuleFieldType.TEXT, []))
assert.include(
source,
"import { FormElement, FormGrid, GridElement, InputText } from '#shared/forms/composed';"
)
})
})
@@ -0,0 +1,76 @@
import { test } from '@japa/runner'
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { writeFrontendModule } from '#services/module_generator/writers/frontend_module_writer'
const TEMPLATE_DIR = join(process.cwd(), 'app', 'templates', 'frontend', 'module')
test.group('writeFrontendModule', (group) => {
let outDir: string
group.each.setup(() => {
outDir = mkdtempSync(join(tmpdir(), 'module-writer-'))
return () => rmSync(outDir, { recursive: true, force: true })
})
test('renders hbs files with context and strips the extension', ({ assert }) => {
writeFrontendModule({
templateDir: TEMPLATE_DIR,
outDir,
context: {
name: 'post',
fields: [{ name: 'title', type: 'string' }],
},
overrides: {},
})
assert.isFalse(existsSync(join(outDir, 'config', 'meta.ts.hbs')))
const meta = readFileSync(join(outDir, 'config', 'meta.ts'), 'utf8')
assert.include(meta, "export const MODULE_NAME = 'post'")
const entity = readFileSync(join(outDir, 'entities', 'Entity.ts'), 'utf8')
assert.include(entity, 'title: string')
})
test('copies non-hbs files verbatim, preserving nesting', ({ assert }) => {
writeFrontendModule({
templateDir: TEMPLATE_DIR,
outDir,
context: { name: 'x' },
overrides: {},
})
const template = readFileSync(join(TEMPLATE_DIR, 'view', 'widgets', 'DataTable.tsx'), 'utf8')
const copied = readFileSync(join(outDir, 'view', 'widgets', 'DataTable.tsx'), 'utf8')
assert.equal(copied, template)
})
test('writes overrides on top of the scaffold and keeps defaults for null overrides', ({
assert,
}) => {
writeFrontendModule({
templateDir: TEMPLATE_DIR,
outDir,
context: { name: 'x' },
overrides: {
'view/widgets/DataTable.tsx': '// generated',
'view/forms/MainForm/Component.tsx': null,
},
})
assert.equal(
readFileSync(join(outDir, 'view', 'widgets', 'DataTable.tsx'), 'utf8'),
'// generated'
)
const scaffoldForm = readFileSync(
join(TEMPLATE_DIR, 'view', 'forms', 'MainForm', 'Component.tsx'),
'utf8'
)
assert.equal(
readFileSync(join(outDir, 'view', 'forms', 'MainForm', 'Component.tsx'), 'utf8'),
scaffoldForm
)
})
})