@things-factory/organization 10.1.167 → 10.1.168
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/component/approval-line-edit.d.ts +54 -0
- package/dist-client/component/approval-line-edit.js +66 -0
- package/dist-client/component/approval-line-edit.js.map +1 -0
- package/dist-client/component/approval-line-items-editor.d.ts +3 -0
- package/dist-client/component/approval-line-items-editor.js +26 -34
- package/dist-client/component/approval-line-items-editor.js.map +1 -1
- package/dist-client/component/approval-line-selector.d.ts +2 -1
- package/dist-client/component/approval-line-selector.js +8 -2
- package/dist-client/component/approval-line-selector.js.map +1 -1
- package/dist-client/pages/approval-line/common-approval-line-templates-page.js +4 -0
- package/dist-client/pages/approval-line/common-approval-line-templates-page.js.map +1 -1
- package/dist-client/pages/approval-line/my-approval-line-templates-page.js +4 -0
- package/dist-client/pages/approval-line/my-approval-line-templates-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/tests/approval-line-edit.test.ts +78 -0
- package/tests/approval-line-step-fields.test.ts +108 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/organization",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.168",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"browser": "dist-client/index.js",
|
|
6
6
|
"things-factory": true,
|
|
@@ -41,5 +41,5 @@
|
|
|
41
41
|
"@things-factory/contact": "^10.1.167",
|
|
42
42
|
"@things-factory/shell": "^10.1.165"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "fe067ff11d90effffc2523374453d9b8313b489f"
|
|
45
45
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
lineAfterCopy,
|
|
3
|
+
lineFromRows,
|
|
4
|
+
participationChoice,
|
|
5
|
+
PARTICIPATION_CHOICES
|
|
6
|
+
} from '../client/component/approval-line-edit'
|
|
7
|
+
|
|
8
|
+
const ROLE = { id: 'role-1', name: '대표' }
|
|
9
|
+
const PERSON = { id: 'employee-1', name: '홍길동' }
|
|
10
|
+
|
|
11
|
+
describe('the line the editor hands on is read off the rows on screen', () => {
|
|
12
|
+
it('keeps type, approver, participation and notifyAfter, in row order', () => {
|
|
13
|
+
expect(
|
|
14
|
+
lineFromRows([
|
|
15
|
+
{ type: 'Role', approver: ROLE, participation: '' },
|
|
16
|
+
{ type: 'Employee', approver: PERSON, participation: 'reference', notifyAfter: '2' }
|
|
17
|
+
])
|
|
18
|
+
).toEqual([
|
|
19
|
+
{ type: 'Role', approver: ROLE, participation: undefined, notifyAfter: undefined },
|
|
20
|
+
{ type: 'Employee', approver: PERSON, participation: 'reference', notifyAfter: 2 }
|
|
21
|
+
])
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('drops a row with no type - an added row nobody filled', () => {
|
|
25
|
+
expect(lineFromRows([{ type: 'Role', approver: ROLE }, { approver: PERSON }, {}])).toHaveLength(1)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('reads the API spelling of a reference', () => {
|
|
29
|
+
expect(lineFromRows([{ type: 'Role', approver: ROLE, participation: 'Reference' }])[0].participation).toBe('reference')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('no rows: an empty line', () => {
|
|
33
|
+
expect(lineFromRows(undefined)).toEqual([])
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('copying a saved line', () => {
|
|
38
|
+
const current = [{ type: 'Role', approver: ROLE }] as any
|
|
39
|
+
|
|
40
|
+
it('nothing picked: the line being edited stays as it is', () => {
|
|
41
|
+
expect(lineAfterCopy(current, undefined)).toBe(current)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('emptied on purpose: an empty line', () => {
|
|
45
|
+
expect(lineAfterCopy(current, null)).toEqual([])
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('a picked line comes over with its reference steps and their notify point', () => {
|
|
49
|
+
const picked = {
|
|
50
|
+
model: [
|
|
51
|
+
{ type: 'Role', approver: ROLE },
|
|
52
|
+
{ type: 'Employee', approver: PERSON, participation: 'Reference', notifyAfter: 1 }
|
|
53
|
+
]
|
|
54
|
+
} as any
|
|
55
|
+
|
|
56
|
+
expect(lineAfterCopy(current, picked)).toEqual([
|
|
57
|
+
{ type: 'Role', approver: ROLE, participation: undefined, notifyAfter: undefined },
|
|
58
|
+
{ type: 'Employee', approver: PERSON, participation: 'reference', notifyAfter: 1 }
|
|
59
|
+
])
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('the participation cell names approval too', () => {
|
|
64
|
+
it('a step with nothing stored reads as approval', () => {
|
|
65
|
+
expect(participationChoice({}).key).toBe('label.participation-approval')
|
|
66
|
+
expect(participationChoice({ participation: undefined }).key).toBe('label.participation-approval')
|
|
67
|
+
expect(participationChoice({ participation: null }).key).toBe('label.participation-approval')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('a reference reads as reference, in either spelling', () => {
|
|
71
|
+
expect(participationChoice({ participation: 'reference' }).key).toBe('label.participation-reference')
|
|
72
|
+
expect(participationChoice({ participation: 'Reference' }).key).toBe('label.participation-reference')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('every choice the cell can draw is a choice the select offers', () => {
|
|
76
|
+
expect(PARTICIPATION_CHOICES.map(one => one.value)).toEqual(['', 'reference'])
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Every screen that reads an approval line step asks for every field the step carries.
|
|
6
|
+
*
|
|
7
|
+
* The approval line editor keeps `type`, `approver`, `participation` and `notifyAfter` of a step
|
|
8
|
+
* (approval-line-edit's lineFromRows; `value` is left to `approver` on purpose). Screens asked for
|
|
9
|
+
* some of them. A screen that reopens a line and saves it again
|
|
10
|
+
* writes back only what it read, so a missing `participation` turns a reference step into an
|
|
11
|
+
* approver and a missing `notifyAfter` wipes the notify point - with no error. Found so far: the
|
|
12
|
+
* worklist screens (notifyAfter, twelve selections), the copy-from selector and the two template
|
|
13
|
+
* pages here (both), and meta-ui's activity definition (both).
|
|
14
|
+
*
|
|
15
|
+
* A gql`` document is a template literal, so type checking cannot see a missing field. This reads the
|
|
16
|
+
* documents of every package's client: any `approvalLine { }` or `model { }` selection that asks for
|
|
17
|
+
* `approver` is a step selection.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const packagesDir = path.resolve(__dirname, '../..')
|
|
21
|
+
|
|
22
|
+
function sources(dir: string): string[] {
|
|
23
|
+
return readdirSync(dir).flatMap(name => {
|
|
24
|
+
if (name === 'node_modules' || name.startsWith('dist')) {
|
|
25
|
+
return []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const file = path.join(dir, name)
|
|
29
|
+
|
|
30
|
+
if (statSync(file).isDirectory()) {
|
|
31
|
+
return sources(file)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return file.endsWith('.ts') ? [file] : []
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The top-level field names of each `approvalLine { }` / `model { }` selection in a source. */
|
|
39
|
+
function selections(source: string): string[][] {
|
|
40
|
+
const found: string[][] = []
|
|
41
|
+
|
|
42
|
+
for (const match of source.matchAll(/\b(?:approvalLine|model)\s*\{/g)) {
|
|
43
|
+
const fields: string[] = []
|
|
44
|
+
let depth = 0
|
|
45
|
+
let word = ''
|
|
46
|
+
|
|
47
|
+
const take = () => {
|
|
48
|
+
if (depth === 1 && word) fields.push(word)
|
|
49
|
+
word = ''
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (let at = match.index! + match[0].length - 1; at < source.length; at++) {
|
|
53
|
+
const char = source[at]
|
|
54
|
+
|
|
55
|
+
if (char === '{') {
|
|
56
|
+
take()
|
|
57
|
+
depth++
|
|
58
|
+
} else if (char === '}') {
|
|
59
|
+
take()
|
|
60
|
+
if (!--depth) break
|
|
61
|
+
} else if (/\w/.test(char)) {
|
|
62
|
+
word += char
|
|
63
|
+
} else {
|
|
64
|
+
take()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
found.push(fields)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return found
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** What the editor keeps of a step - read less and the next save loses the rest. */
|
|
75
|
+
const STEP_FIELDS = ['type', 'participation', 'notifyAfter', 'approver']
|
|
76
|
+
|
|
77
|
+
const clients = readdirSync(packagesDir)
|
|
78
|
+
.map(name => path.join(packagesDir, name, 'client'))
|
|
79
|
+
.filter(dir => statSync(path.dirname(dir)).isDirectory() && readdirSync(path.dirname(dir)).includes('client'))
|
|
80
|
+
|
|
81
|
+
const screens = clients
|
|
82
|
+
.flatMap(sources)
|
|
83
|
+
.map(file => ({
|
|
84
|
+
file: path.relative(packagesDir, file),
|
|
85
|
+
steps: selections(readFileSync(file, 'utf-8')).filter(fields => fields.includes('approver'))
|
|
86
|
+
}))
|
|
87
|
+
.filter(one => one.steps.length)
|
|
88
|
+
|
|
89
|
+
describe('every screen that reads an approval line step asks for all of its fields', () => {
|
|
90
|
+
it('the scan finds the screens', () => {
|
|
91
|
+
expect(screens.map(one => one.file)).toEqual(
|
|
92
|
+
expect.arrayContaining([
|
|
93
|
+
'organization/client/component/approval-line-selector.ts',
|
|
94
|
+
'organization/client/pages/approval-line/my-approval-line-templates-page.ts',
|
|
95
|
+
'worklist/client/pages/activity-catalog/activity-detail-page.ts'
|
|
96
|
+
])
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it.each(screens.map(one => [one.file, one.steps] as [string, string[][]]))('%s', (file, steps) => {
|
|
101
|
+
for (const fields of steps) {
|
|
102
|
+
expect({ asks: fields, missing: STEP_FIELDS.filter(field => !fields.includes(field)) }).toEqual({
|
|
103
|
+
asks: fields,
|
|
104
|
+
missing: []
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
})
|