@things-factory/figure-ui 10.1.111 → 10.1.113
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/client/graphql/store.ts +3 -3
- package/client/modeller/product-session.ts +6 -3
- package/client/modeller/thumbnail-hash.ts +16 -0
- package/dist-client/graphql/store.js +2 -2
- package/dist-client/graphql/store.js.map +1 -1
- package/dist-client/modeller/product-session.d.ts +1 -1
- package/dist-client/modeller/product-session.js +5 -2
- package/dist-client/modeller/product-session.js.map +1 -1
- package/dist-client/modeller/thumbnail-hash.d.ts +2 -0
- package/dist-client/modeller/thumbnail-hash.js +18 -0
- package/dist-client/modeller/thumbnail-hash.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/test/save-landed.test.ts +22 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-ui",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.113",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"@operato/shell": "^10.0.0",
|
|
41
41
|
"@operato/styles": "^10.0.0",
|
|
42
42
|
"@operato/utils": "^10.0.0",
|
|
43
|
-
"@things-factory/ai-assistant": "^10.1.
|
|
44
|
-
"@things-factory/figure-service": "^10.1.
|
|
43
|
+
"@things-factory/ai-assistant": "^10.1.113",
|
|
44
|
+
"@things-factory/figure-service": "^10.1.113",
|
|
45
45
|
"three": "^0.185.1"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "9da1e467ddce23e600e64f08454289cb3b806212"
|
|
48
48
|
}
|
package/test/save-landed.test.ts
CHANGED
|
@@ -10,8 +10,11 @@ import { fileURLToPath } from 'node:url'
|
|
|
10
10
|
import { addFigureBox, createFigureAsset, serializeFigureAsset } from '@hatiolab/figure-model'
|
|
11
11
|
|
|
12
12
|
import { createFigureProductSession } from '../client/modeller/product-session.js'
|
|
13
|
+
import { thumbnailHashOf } from '../client/modeller/thumbnail-hash.js'
|
|
13
14
|
|
|
14
15
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
16
|
+
/** A picture as the modeller sends one: a data URL. */
|
|
17
|
+
const pic = (word: string) => `data:image/webp;base64,${Buffer.from(word).toString('base64')}`
|
|
15
18
|
const unreachable = () => Object.assign(Error('서버에 연결하지 못했습니다.'), { code: 'SAVE_UNREACHABLE' })
|
|
16
19
|
|
|
17
20
|
/** A server that keeps one row; its save can be made to fail before or after it writes. */
|
|
@@ -23,7 +26,8 @@ function server() {
|
|
|
23
26
|
load: async (id: string) => ({ id, source: row }),
|
|
24
27
|
readBack: async (id: string) => {
|
|
25
28
|
if (state.loadFails) throw unreachable()
|
|
26
|
-
|
|
29
|
+
/* The server hands back the name it keeps the picture under, never the bytes. */
|
|
30
|
+
return { id, source: row, thumbnailHash: picture === undefined ? undefined : await thumbnailHashOf(picture) }
|
|
27
31
|
},
|
|
28
32
|
save: async (q: { id?: string; source: string; expectedSource?: string; thumbnail?: string }) => {
|
|
29
33
|
if (state.hold) await state.hold
|
|
@@ -131,9 +135,9 @@ test('a refusal on grounds is not a save, even if the row holds the same text; a
|
|
|
131
135
|
await other.open('a')
|
|
132
136
|
other.command({ kind: 'rename', name: 'B' } as never)
|
|
133
137
|
s2.setRow(serializeFigureAsset(JSON.parse(other.source())))
|
|
134
|
-
s2.setPicture('old-picture')
|
|
138
|
+
s2.setPicture(pic('old-picture'))
|
|
135
139
|
s2.state.failBeforeWrite = true
|
|
136
|
-
await assert.rejects(other.save({ thumbnail: 'new-picture' }), (e: { code?: string }) => e.code === 'SAVE_UNREACHABLE')
|
|
140
|
+
await assert.rejects(other.save({ thumbnail: pic('new-picture') }), (e: { code?: string }) => e.code === 'SAVE_UNREACHABLE')
|
|
137
141
|
assert.equal(other.read().dirty, true)
|
|
138
142
|
})
|
|
139
143
|
|
|
@@ -187,12 +191,24 @@ test('the sent content confirmed on the server means every field of it, the pict
|
|
|
187
191
|
session.command({ kind: 'rename', name: 'B' } as never)
|
|
188
192
|
s.state.failAfterWrite = true
|
|
189
193
|
s.state.loadFails = true
|
|
190
|
-
await assert.rejects(session.save({ thumbnail: 'mine' }), (e: { code?: string }) => e.code === 'SAVE_UNREACHABLE')
|
|
194
|
+
await assert.rejects(session.save({ thumbnail: pic('mine') }), (e: { code?: string }) => e.code === 'SAVE_UNREACHABLE')
|
|
191
195
|
s.state.failAfterWrite = false
|
|
192
196
|
s.state.loadFails = false
|
|
193
197
|
/* Someone else changed the picture since: the source matches what was sent, the picture does not. */
|
|
194
|
-
s.setPicture('theirs')
|
|
198
|
+
s.setPicture(pic('theirs'))
|
|
195
199
|
session.command({ kind: 'rename', name: 'C' } as never)
|
|
196
|
-
await assert.rejects(session.save({ thumbnail: 'mine-2' }), /다른 편집에서/)
|
|
200
|
+
await assert.rejects(session.save({ thumbnail: pic('mine-2') }), /다른 편집에서/)
|
|
197
201
|
assert.equal(session.read().dirty, true)
|
|
198
202
|
})
|
|
203
|
+
|
|
204
|
+
/* ADR-0104 decision 6: the server keeps the picture by the hash of its bytes; read back, the sent picture is matched by it. */
|
|
205
|
+
test('the answer lost after the picture was written: read back by its hash, it was saved', async () => {
|
|
206
|
+
const s = server()
|
|
207
|
+
const session = createFigureProductSession(s.store)
|
|
208
|
+
await session.open('a')
|
|
209
|
+
session.command({ kind: 'rename', name: 'B' } as never)
|
|
210
|
+
s.state.failAfterWrite = true
|
|
211
|
+
await session.save({ thumbnail: pic('card') })
|
|
212
|
+
assert.equal(session.read().dirty, false, 'source and picture both landed')
|
|
213
|
+
assert.equal(await thumbnailHashOf('not a picture'), undefined)
|
|
214
|
+
})
|