diff --git a/frontend/src/pages/entity-record-viewer.vue b/frontend/src/pages/entity-record-viewer.vue
index 82e3e25..ba86229 100644
--- a/frontend/src/pages/entity-record-viewer.vue
+++ b/frontend/src/pages/entity-record-viewer.vue
@@ -1,5 +1,5 @@
diff --git a/frontend/tests/user-file.spec.ts b/frontend/tests/user-file.spec.ts
index e95e9df..0eeb42b 100644
--- a/frontend/tests/user-file.spec.ts
+++ b/frontend/tests/user-file.spec.ts
@@ -14,6 +14,8 @@ test('admin opens a user file and views a linked entity record', async ({ page }
let projectTitle = 'Website refresh'
const projects: Record[] = [{ id: 42, title: 'Website refresh', owner: 7 }]
const tasks: Record[] = []
+ const notes: Record[] = []
+ const milestones: Record[] = []
const projectResponse = () => ({
entity: {
id: 3,
@@ -22,6 +24,14 @@ test('admin opens a user file and views a linked entity record', async ({ page }
fields: [
{ id: 11, name: 'Title', identifier: 'title', type: 'TEXT' },
{ id: 12, name: 'Owner', identifier: 'owner', type: 'USER' },
+ {
+ id: 13,
+ name: 'Milestones',
+ identifier: 'milestones',
+ type: 'RELATIONSHIP',
+ targetEntityId: 6,
+ relationshipType: 'ONE_TO_MANY',
+ },
],
},
values: { id: 42, title: projectTitle, owner: 7 },
@@ -49,6 +59,23 @@ test('admin opens a user file and views a linked entity record', async ({ page }
targetEntityId: 3,
},
records: tasks,
+ }, {
+ entity: {
+ id: 6,
+ name: 'Milestone',
+ identifier: 'milestone',
+ fields: [{ id: 40, name: 'Label', identifier: 'label', type: 'TEXT' }],
+ },
+ relationshipField: {
+ id: 13,
+ name: 'Milestones',
+ identifier: 'milestones',
+ type: 'RELATIONSHIP',
+ targetEntityId: 6,
+ relationshipType: 'ONE_TO_MANY',
+ },
+ relationshipDirection: 'OUTBOUND',
+ records: milestones,
}],
})
@@ -90,7 +117,7 @@ test('admin opens a user file and views a linked entity record', async ({ page }
}
await route.fulfill({ json: projectResponse() })
})
- await page.route('**/api/users/7/entities/Project/records/42/children/Task/21', async route => {
+ await page.route('**/api/users/7/entities/Project/records/42/children/Task/21*', async route => {
const values = new URLSearchParams(route.request().postData() ?? '')
const created = { id: 55, description: values.get('description'), project: 42 }
tasks.push(created)
@@ -103,6 +130,50 @@ test('admin opens a user file and views a linked entity record', async ({ page }
},
})
})
+ await page.route('**/api/users/7/entities/Project/records/42/children/Milestone/13*', async route => {
+ expect(new URL(route.request().url()).searchParams.get('direction')).toBe('OUTBOUND')
+ const values = new URLSearchParams(route.request().postData() ?? '')
+ const created = { id: 60, label: values.get('label') }
+ milestones.push(created)
+ await route.fulfill({
+ status: 201,
+ json: { entity: projectResponse().children.at(1)!.entity, values: created, children: [] },
+ })
+ })
+ await page.route('**/api/users/7/entities/Task/records/55', route => route.fulfill({
+ json: {
+ entity: projectResponse().children.at(0)!.entity,
+ values: tasks.at(0),
+ children: [{
+ entity: {
+ id: 5,
+ name: 'Note',
+ identifier: 'note',
+ fields: [
+ { id: 30, name: 'Contents', identifier: 'contents', type: 'TEXT' },
+ { id: 31, name: 'Task', identifier: 'task', type: 'RELATIONSHIP', targetEntityId: 4 },
+ ],
+ },
+ relationshipField: {
+ id: 31,
+ name: 'Task',
+ identifier: 'task',
+ type: 'RELATIONSHIP',
+ targetEntityId: 4,
+ },
+ records: notes,
+ }],
+ },
+ }))
+ await page.route('**/api/users/7/entities/Task/records/55/children/Note/31*', async route => {
+ const values = new URLSearchParams(route.request().postData() ?? '')
+ const created = { id: 56, contents: values.get('contents'), task: 55 }
+ notes.push(created)
+ await route.fulfill({
+ status: 201,
+ json: { entity: { id: 5, name: 'Note', fields: [] }, values: created, children: [] },
+ })
+ })
await page.goto('/users')
await page.getByRole('link', { name: 'alex' }).click()
@@ -123,6 +194,12 @@ test('admin opens a user file and views a linked entity record', async ({ page }
await expect(page.getByText('Website refresh')).toBeVisible()
await expect(page.getByText('Owner')).toBeVisible()
+ await page.getByRole('button', { name: 'Add Milestone' }).click()
+ const milestoneForm = page.getByRole('form', { name: 'Add Milestone' })
+ await milestoneForm.getByLabel('Label').fill('Public beta')
+ await milestoneForm.getByRole('button', { name: 'Add Milestone' }).click()
+ await expect(page.getByRole('row').filter({ hasText: 'Public beta' })).toBeVisible()
+
await page.getByRole('button', { name: 'Edit record' }).click()
const editForm = page.getByRole('form', { name: 'Edit record' })
await editForm.getByLabel('Title').fill('Mobile application')
@@ -134,4 +211,12 @@ test('admin opens a user file and views a linked entity record', async ({ page }
await childForm.getByLabel('Description').fill('Prepare launch')
await childForm.getByRole('button', { name: 'Add Task' }).click()
await expect(page.getByRole('row').filter({ hasText: 'Prepare launch' })).toContainText('42')
+
+ await page.getByRole('link', { name: 'View Task record 55' }).click()
+ await expect(page.getByRole('heading', { name: 'Task #55' })).toBeVisible()
+ await page.getByRole('button', { name: 'Add Note' }).click()
+ const noteForm = page.getByRole('form', { name: 'Add Note' })
+ await noteForm.getByLabel('Contents').fill('Nested child')
+ await noteForm.getByRole('button', { name: 'Add Note' }).click()
+ await expect(page.getByRole('row').filter({ hasText: 'Nested child' })).toContainText('55')
})
diff --git a/src/main/kotlin/dev/mduchene/bolts/entity/EntityRecordRepository.kt b/src/main/kotlin/dev/mduchene/bolts/entity/EntityRecordRepository.kt
index e44b290..af08607 100644
--- a/src/main/kotlin/dev/mduchene/bolts/entity/EntityRecordRepository.kt
+++ b/src/main/kotlin/dev/mduchene/bolts/entity/EntityRecordRepository.kt
@@ -22,8 +22,11 @@ data class ChildEntityRecords(
val entity: EntityDefinition,
val relationshipField: EntityField,
val records: List