@ossy/platform 3.9.0 → 3.11.1

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.
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Normalize a declarative flow `files` step into Playwright `setInputFiles` payloads.
3
+ * Use for native file inputs (`type=file`) — form fills intentionally skip file fields.
4
+ *
5
+ * @param {unknown} files
6
+ * @returns {{
7
+ * selector: string,
8
+ * payloads: Array<string | { name: string, mimeType: string, buffer: Buffer }>
9
+ * }}
10
+ */
11
+ export function resolveFilesTarget (files) {
12
+ if (files == null || typeof files !== 'object' || Array.isArray(files)) {
13
+ throw new Error('files step requires { selector, paths? } or { selector, items? }')
14
+ }
15
+
16
+ const selector = typeof files.selector === 'string' ? files.selector.trim() : ''
17
+ if (!selector) {
18
+ throw new Error('files step requires a non-empty CSS selector')
19
+ }
20
+
21
+ const paths = normalizePathList(files.paths ?? files.path)
22
+ const items = normalizeItemList(files.items ?? files.item)
23
+
24
+ if (paths.length === 0 && items.length === 0) {
25
+ throw new Error('files step requires at least one path or item')
26
+ }
27
+
28
+ /** @type {Array<string | { name: string, mimeType: string, buffer: Buffer }>} */
29
+ const payloads = [
30
+ ...paths,
31
+ ...items.map((item) => ({
32
+ name: item.name,
33
+ mimeType: item.mimeType,
34
+ buffer: Buffer.from(item.content, item.encoding ?? 'utf8'),
35
+ })),
36
+ ]
37
+
38
+ return { selector, payloads }
39
+ }
40
+
41
+ /**
42
+ * @param {unknown} raw
43
+ * @returns {string[]}
44
+ */
45
+ function normalizePathList (raw) {
46
+ if (raw == null) return []
47
+ const list = Array.isArray(raw) ? raw : [raw]
48
+ return list.map((entry, index) => {
49
+ if (typeof entry !== 'string' || !entry.trim()) {
50
+ throw new Error(`files.paths[${index}] must be a non-empty string`)
51
+ }
52
+ return entry.trim()
53
+ })
54
+ }
55
+
56
+ /**
57
+ * @param {unknown} raw
58
+ * @returns {Array<{ name: string, mimeType: string, content: string, encoding?: BufferEncoding }>}
59
+ */
60
+ function normalizeItemList (raw) {
61
+ if (raw == null) return []
62
+ const list = Array.isArray(raw) ? raw : [raw]
63
+ return list.map((entry, index) => {
64
+ if (entry == null || typeof entry !== 'object' || Array.isArray(entry)) {
65
+ throw new Error(`files.items[${index}] must be an object`)
66
+ }
67
+ const name = typeof entry.name === 'string' ? entry.name.trim() : ''
68
+ if (!name) {
69
+ throw new Error(`files.items[${index}].name must be a non-empty string`)
70
+ }
71
+ if (typeof entry.content !== 'string') {
72
+ throw new Error(`files.items[${index}].content must be a string`)
73
+ }
74
+ const mimeType = typeof entry.mimeType === 'string' && entry.mimeType.trim()
75
+ ? entry.mimeType.trim()
76
+ : 'application/octet-stream'
77
+ const encoding = entry.encoding == null ? undefined : entry.encoding
78
+ if (encoding != null && typeof encoding !== 'string') {
79
+ throw new Error(`files.items[${index}].encoding must be a string when set`)
80
+ }
81
+ return { name, mimeType, content: entry.content, encoding }
82
+ })
83
+ }
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from '@jest/globals'
2
+ import { resolveFilesTarget } from './flow-files.js'
3
+
4
+ describe('resolveFilesTarget', () => {
5
+ it('accepts a single filesystem path', () => {
6
+ expect(resolveFilesTarget({
7
+ selector: ' #upload-resources ',
8
+ path: ' /tmp/sample.txt ',
9
+ })).toEqual({
10
+ selector: '#upload-resources',
11
+ payloads: ['/tmp/sample.txt'],
12
+ })
13
+ })
14
+
15
+ it('accepts multiple filesystem paths', () => {
16
+ expect(resolveFilesTarget({
17
+ selector: '#upload-resources',
18
+ paths: ['a.txt', 'b.txt'],
19
+ })).toEqual({
20
+ selector: '#upload-resources',
21
+ payloads: ['a.txt', 'b.txt'],
22
+ })
23
+ })
24
+
25
+ it('accepts in-memory buffer items', () => {
26
+ const resolved = resolveFilesTarget({
27
+ selector: '#upload-resources',
28
+ items: [{
29
+ name: 'e2e-upload.txt',
30
+ mimeType: 'text/plain',
31
+ content: 'hello from e2e',
32
+ }],
33
+ })
34
+ expect(resolved.selector).toBe('#upload-resources')
35
+ expect(resolved.payloads).toHaveLength(1)
36
+ expect(resolved.payloads[0]).toMatchObject({
37
+ name: 'e2e-upload.txt',
38
+ mimeType: 'text/plain',
39
+ })
40
+ expect(Buffer.isBuffer(resolved.payloads[0].buffer)).toBe(true)
41
+ expect(resolved.payloads[0].buffer.toString('utf8')).toBe('hello from e2e')
42
+ })
43
+
44
+ it('defaults mimeType for buffer items', () => {
45
+ const resolved = resolveFilesTarget({
46
+ selector: '[data-ossy-upload-input]',
47
+ item: { name: 'blob.bin', content: 'x' },
48
+ })
49
+ expect(resolved.payloads[0].mimeType).toBe('application/octet-stream')
50
+ })
51
+
52
+ it('rejects empty or invalid values', () => {
53
+ expect(() => resolveFilesTarget(null)).toThrow(/requires \{ selector/)
54
+ expect(() => resolveFilesTarget({ selector: '' })).toThrow(/non-empty CSS selector/)
55
+ expect(() => resolveFilesTarget({ selector: '#x' })).toThrow(/at least one path or item/)
56
+ expect(() => resolveFilesTarget({
57
+ selector: '#x',
58
+ paths: [''],
59
+ })).toThrow(/files\.paths\[0\]/)
60
+ expect(() => resolveFilesTarget({
61
+ selector: '#x',
62
+ items: [{ name: '', content: 'x' }],
63
+ })).toThrow(/files\.items\[0\]\.name/)
64
+ expect(() => resolveFilesTarget({
65
+ selector: '#x',
66
+ items: [{ name: 'a.txt', content: 1 }],
67
+ })).toThrow(/files\.items\[0\]\.content/)
68
+ })
69
+ })