turtb 0.5.0

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.
Files changed (70) hide show
  1. package/LICENSE +661 -0
  2. package/bin/turtb.js +259 -0
  3. package/lib/display/h.js +204 -0
  4. package/lib/display/helpers.js +78 -0
  5. package/lib/display/render.js +177 -0
  6. package/lib/display/shapes.js +91 -0
  7. package/lib/turtle/Signer.js +120 -0
  8. package/lib/turtle/Signer.test.js +38 -0
  9. package/lib/turtle/TurtleBranch.js +147 -0
  10. package/lib/turtle/TurtleBranch.test.js +89 -0
  11. package/lib/turtle/TurtleDictionary.js +157 -0
  12. package/lib/turtle/TurtleDictionary.test.js +331 -0
  13. package/lib/turtle/U8aTurtle.js +203 -0
  14. package/lib/turtle/U8aTurtle.test.js +60 -0
  15. package/lib/turtle/Workspace.js +62 -0
  16. package/lib/turtle/Workspace.test.js +63 -0
  17. package/lib/turtle/codecs/CodecType.js +36 -0
  18. package/lib/turtle/codecs/CodecTypeVersion.js +37 -0
  19. package/lib/turtle/codecs/Commit.js +10 -0
  20. package/lib/turtle/codecs/CompositeCodec.js +86 -0
  21. package/lib/turtle/codecs/TreeNode.js +38 -0
  22. package/lib/turtle/codecs/codec.js +441 -0
  23. package/lib/turtle/connections/AbstractUpdater.js +176 -0
  24. package/lib/turtle/connections/TurtleBranchMultiplexer.js +102 -0
  25. package/lib/turtle/connections/TurtleBranchMultiplexer.test.js +26 -0
  26. package/lib/turtle/connections/TurtleBranchUpdater.js +47 -0
  27. package/lib/turtle/connections/TurtleDB.js +165 -0
  28. package/lib/turtle/connections/TurtleDB.test.js +45 -0
  29. package/lib/turtle/connections/TurtleTalker.js +34 -0
  30. package/lib/turtle/connections/TurtleTalker.test.js +101 -0
  31. package/lib/turtle/utils.js +192 -0
  32. package/lib/turtle/utils.test.js +158 -0
  33. package/lib/utils/Assert.js +115 -0
  34. package/lib/utils/NestedSet.js +68 -0
  35. package/lib/utils/NestedSet.test.js +30 -0
  36. package/lib/utils/OWN_KEYS.js +1 -0
  37. package/lib/utils/Recaller.js +175 -0
  38. package/lib/utils/Recaller.test.js +75 -0
  39. package/lib/utils/TestRunner.js +200 -0
  40. package/lib/utils/TestRunner.test.js +144 -0
  41. package/lib/utils/TestRunnerConstants.js +13 -0
  42. package/lib/utils/combineUint8ArrayLikes.js +18 -0
  43. package/lib/utils/combineUint8Arrays.js +23 -0
  44. package/lib/utils/components.js +88 -0
  45. package/lib/utils/crypto.js +17 -0
  46. package/lib/utils/deepEqual.js +16 -0
  47. package/lib/utils/deepEqual.test.js +27 -0
  48. package/lib/utils/fileTransformer.js +16 -0
  49. package/lib/utils/handleRedirect.js +93 -0
  50. package/lib/utils/logger.js +24 -0
  51. package/lib/utils/nextTick.js +47 -0
  52. package/lib/utils/noble-secp256k1.js +602 -0
  53. package/lib/utils/proxyWithRecaller.js +51 -0
  54. package/lib/utils/toCombinedVersion.js +14 -0
  55. package/lib/utils/toSubVersions.js +14 -0
  56. package/lib/utils/toVersionCount.js +5 -0
  57. package/lib/utils/webSocketMuxFactory.js +123 -0
  58. package/lib/utils/zabacaba.js +25 -0
  59. package/package.json +24 -0
  60. package/src/ArchiveUpdater.js +99 -0
  61. package/src/S3Updater.js +99 -0
  62. package/src/archiveSync.js +28 -0
  63. package/src/fileSync.js +155 -0
  64. package/src/getExistenceLength.js +19 -0
  65. package/src/manageCert.js +36 -0
  66. package/src/originSync.js +75 -0
  67. package/src/outletSync.js +50 -0
  68. package/src/proxyFolder.js +195 -0
  69. package/src/s3Sync.js +32 -0
  70. package/src/webSync.js +101 -0
@@ -0,0 +1,175 @@
1
+ import { logError, logInfo, logWarn } from './logger.js'
2
+ import { NestedSet } from './NestedSet.js'
3
+ import { nextTick, getTickCount } from './nextTick.js'
4
+
5
+ let _debug = false
6
+ export const setDebug = (debug = true) => {
7
+ _debug = debug
8
+ }
9
+
10
+ export const IGNORE_ACCESS = Symbol('IGNORE_ACCESS')
11
+ export const IGNORE_MUTATE = Symbol('IGNORE_MUTATE')
12
+ export const IGNORE = Symbol('IGNORE')
13
+
14
+ const invert = msg => `\x1b[7m${msg}\x1b[0m`
15
+
16
+ export class Recaller {
17
+ name
18
+ #mms = new NestedSet()
19
+ #functionNames = new Map()
20
+ #stack = []
21
+ #triggered = new Set()
22
+ #handlingTriggered = false
23
+ #beforeTriggered = []
24
+ #afterTriggered = []
25
+ /** @type {boolean} */
26
+ #debug
27
+ #ignore = null
28
+ loopWarn = 2
29
+ loopLimit = 10
30
+
31
+ constructor (name) {
32
+ if (!name) throw new Error('Recaller must be named')
33
+ this.name = name
34
+ }
35
+
36
+ set debug (debug) {
37
+ this.#debug = debug
38
+ }
39
+
40
+ get debug () {
41
+ return this.#debug ?? _debug
42
+ }
43
+
44
+ call (f, ignore = null) {
45
+ if (typeof f !== 'function') throw new Error('can only hide functions')
46
+ const previousIgnore = this.#ignore
47
+ this.#ignore = ignore
48
+ const v = f(this)
49
+ this.#ignore = previousIgnore
50
+ return v
51
+ }
52
+
53
+ watch (name, f) {
54
+ if (!name || typeof name !== 'string') throw new Error('please name watches')
55
+ if (typeof f !== 'function') { throw new Error(`can only watch functions (${name})`) }
56
+ this.#disassociateF(f)
57
+ if (!name) throw new Error('must name function watchers')
58
+ this.#nameFunction(f, name)
59
+ if (this.debug) logInfo(() => console.group(`${invert('<== watching')}: ${JSON.stringify(name)}`))
60
+ this.#stack.unshift(f)
61
+ try {
62
+ f(this)
63
+ } catch (error) {
64
+ logError(() => console.error(error))
65
+ }
66
+ this.#stack.shift()
67
+ if (this.debug) logInfo(() => console.groupEnd())
68
+ }
69
+
70
+ unwatch (f) {
71
+ this.#disassociateF(f)
72
+ }
73
+
74
+ beforeNextUpdate (f) {
75
+ if (!this.#beforeTriggered.includes(f)) this.#beforeTriggered.push(f)
76
+ }
77
+
78
+ afterNextUpdate (f) {
79
+ if (!this.#afterTriggered.includes(f)) this.#afterTriggered.push(f)
80
+ }
81
+
82
+ reportKeyAccess (target, key, method, name) {
83
+ if (this.#ignore === IGNORE || this.#ignore === IGNORE_ACCESS) return
84
+ const f = this.#stack[0]
85
+ if (typeof f !== 'function') return
86
+ name = `${name}['${key.toString()}']`
87
+ if (this.debug) {
88
+ const triggering = this.#getFunctionName(f)
89
+ logInfo(() => console.debug(
90
+ `${invert('<-- access')}: ${JSON.stringify({ recaller: this.name, method, name, triggering })}`
91
+ ))
92
+ }
93
+ this.#associate(f, target, key)
94
+ }
95
+
96
+ reportKeyMutation (target, key, method, name) {
97
+ if (this.#ignore === IGNORE || this.#ignore === IGNORE_MUTATE) return
98
+ const newTriggered = this.#getFunctions(target, key)
99
+ if (!newTriggered.length) return
100
+ name = `${name}['${key.toString()}']`
101
+ if (this.debug) {
102
+ const triggering = newTriggered.map(f => this.#getFunctionName(f))
103
+ logInfo(() => console.debug(
104
+ `${invert('--> mutation')}: ${JSON.stringify({ recaller: this.name, method, name, triggering })}`
105
+ ))
106
+ }
107
+ if (name.match(/\['name'\]\['name'\]/)) throw new Error('double name')
108
+ this.#triggered = new Set([...this.#triggered, ...newTriggered])
109
+ if (this.#handlingTriggered) return
110
+ this.#handlingTriggered = true
111
+ nextTick(() => this.#handleTriggered())
112
+ }
113
+
114
+ #associate (f, target, key) {
115
+ this.#mms.add(key, target, f)
116
+ this.#mms.add(f, target, key)
117
+ }
118
+
119
+ #disassociateF (f) {
120
+ this.#mms.delete(f)
121
+ this.#functionNames.delete(f)
122
+ }
123
+
124
+ #getFunctions (target, key) {
125
+ return this.#mms.values(key, target)
126
+ }
127
+
128
+ // debugging functions
129
+ #nameFunction (f, name) {
130
+ this.#functionNames.set(f, name)
131
+ }
132
+
133
+ #getFunctionName (f) {
134
+ return this.#functionNames.get(f) || f.name || `<<UNNAMED FUNCTION[${f.toString()}]>>`
135
+ }
136
+
137
+ #handleTriggered () {
138
+ const beforeTriggered = [...this.#beforeTriggered]
139
+ this.#beforeTriggered = []
140
+ beforeTriggered.forEach(f => f())
141
+ let loopCounter = 0
142
+ while ((this.#triggered.size || this.#afterTriggered.length)) {
143
+ if (loopCounter >= this.loopLimit) {
144
+ logError(() => console.error(`!! Recaller limit check ERROR; loop count: ${loopCounter}, loop limit: ${this.loopLimit}`))
145
+ break
146
+ }
147
+ if (loopCounter >= this.loopWarn) {
148
+ logWarn(() => console.warn(`!! Recaller loop count: ${loopCounter}`))
149
+ }
150
+ const triggered = this.#triggered
151
+ this.#triggered = new Set()
152
+ if (this.debug) {
153
+ const triggering = [...triggered].map(f => this.#getFunctionName(f))
154
+ logInfo(() => console.time(invert('=== handling triggered group')))
155
+ logInfo(() => console.groupCollapsed(
156
+ `${invert('==> triggering')}: ${JSON.stringify({ recaller: this.name, tickCount: getTickCount(), loopCounter, triggering })}`
157
+ ))
158
+ }
159
+ triggered.forEach(f => {
160
+ const name = this.#getFunctionName(f)
161
+ this.#disassociateF(f)
162
+ this.watch(name, f)
163
+ })
164
+ while (this.#afterTriggered.length) {
165
+ this.#afterTriggered.shift()()
166
+ }
167
+ ++loopCounter
168
+ if (this.debug) {
169
+ logInfo(() => console.groupEnd())
170
+ logInfo(() => console.timeEnd(invert('=== handling triggered group')))
171
+ }
172
+ }
173
+ this.#handlingTriggered = false
174
+ }
175
+ }
@@ -0,0 +1,75 @@
1
+ import { Recaller } from './Recaller.js'
2
+ import { globalTestRunner, urlToName } from './TestRunner.js'
3
+ import { handleNextTick } from './nextTick.js'
4
+
5
+ globalTestRunner.describe(urlToName(import.meta.url), suite => {
6
+ suite.it('calls watched functions when accessed values change', ({ assert }) => {
7
+ const recaller = new Recaller('calls watched functions')
8
+ const a = {}
9
+ const b = {}
10
+ let counter = 0
11
+ recaller.watch('count on access', function () {
12
+ ++counter
13
+ recaller.reportKeyAccess(a, 'x')
14
+ recaller.reportKeyAccess(b, 'y')
15
+ })
16
+ assert.equal(counter, 1, 'function should get called at start')
17
+
18
+ recaller.reportKeyMutation(a, 'x')
19
+ recaller.reportKeyMutation(a, 'x')
20
+ recaller.reportKeyMutation(b, 'y')
21
+ handleNextTick()
22
+ assert.equal(counter, 2, 'function should get called once per tic')
23
+
24
+ recaller.reportKeyMutation(a, 'y')
25
+ recaller.reportKeyMutation(a, 'y')
26
+ recaller.reportKeyMutation(b, 'x')
27
+ handleNextTick()
28
+ assert.equal(counter, 2, 'function should not get called when unaccessed values change')
29
+ })
30
+
31
+ suite.it('calls beforeNextUpdate and afterNextUpdate functions in order', ({ assert }) => {
32
+ const recaller = new Recaller('calls beforeNextUpdate and afterNextUpdate')
33
+ const o = {}
34
+ let output = ''
35
+ recaller.beforeNextUpdate(() => {
36
+ output = output + 'b'
37
+ })
38
+ recaller.afterNextUpdate(() => {
39
+ output = output + 'a'
40
+ })
41
+ recaller.watch('o.x', () => {
42
+ recaller.reportKeyAccess(o, 'x')
43
+ output = output + 'w'
44
+ })
45
+ assert.equal(output, 'w', 'called at start')
46
+ handleNextTick()
47
+ assert.equal(output, 'w', 'nothing triggered')
48
+ recaller.reportKeyMutation(o, 'x')
49
+ assert.equal(output, 'w', 'still nothing triggered')
50
+ handleNextTick()
51
+ assert.equal(output, 'wbwa', 'everything triggered in order')
52
+ })
53
+
54
+ suite.it('skips replaced triggers', ({ assert }) => {
55
+ const recaller = new Recaller('skips replaced triggers')
56
+ let callCount = 0
57
+ const watchedFunction = () => {
58
+ recaller.reportKeyMutation(recaller, 'key', 'test', 'test')
59
+ recaller.reportKeyAccess(recaller, 'key', 'test', 'test')
60
+ ++callCount
61
+ }
62
+ recaller.watch('watchedFunction', watchedFunction)
63
+ assert.equal(callCount, 1)
64
+ handleNextTick()
65
+ assert.equal(callCount, 1)
66
+ recaller.reportKeyMutation(recaller, 'key', 'test', 'test')
67
+ assert.equal(callCount, 1)
68
+ handleNextTick()
69
+ assert.equal(callCount, 2)
70
+ recaller.watch('watchedFunction', watchedFunction)
71
+ assert.equal(callCount, 3)
72
+ handleNextTick()
73
+ assert.equal(callCount, 3)
74
+ })
75
+ })
@@ -0,0 +1,200 @@
1
+ import { TurtleDictionary } from '../turtle/TurtleDictionary.js'
2
+ import { Assert } from './Assert.js'
3
+ import { logError } from './logger.js'
4
+ import { Recaller } from './Recaller.js'
5
+ import { ERROR, FAILED, ONLY, PASSED, RUNNER, RUNNING, SKIP, SKIPPED, SUITE, TEST, WAITING } from './TestRunnerConstants.js'
6
+
7
+ export function urlToName (url) {
8
+ if (typeof window !== 'undefined' && window?.location?.origin && url.startsWith(window.location.origin)) {
9
+ url = url.slice(window.location.origin.length)
10
+ const slashCpk = url.match(/\/[0-9A-Za-z]{50}(?=\/)/)?.[0]
11
+ url = url.slice(slashCpk.length)
12
+ }
13
+ url = /(?<=\/public\/).*/.exec(url)?.[0] ?? url
14
+ const parsed = /(?<path>[^?]*)\.test\.js?.*address=(?<address>[^&]*)/.exec(url)
15
+ if (parsed) url = `${parsed.groups.path} [&${parsed.groups.address}]`
16
+ return url
17
+ }
18
+
19
+ export const testRunnerRecaller = new Recaller('TestRunner')
20
+ // testRunnerRecaller.debug = true
21
+
22
+ export class TestRunnerError extends Error {
23
+ constructor (message, options) {
24
+ super(message, options)
25
+ this.name = this.constructor.name
26
+ }
27
+ }
28
+
29
+ export class TestRunner {
30
+ /** @type {Array.<TestRunner>} */
31
+ #children = []
32
+ /** @type {Promise} */
33
+ #runPromise
34
+ #runChildrenPromise
35
+ #runState
36
+ #f
37
+
38
+ /**
39
+ * @param {string} [name='test-collection']
40
+ * @param {string} [type=RUNNER]
41
+ * @param {TestRunner} parent
42
+ * @param {() => void} f
43
+ * @param {Recaller} [recaller=testRunnerRecaller]
44
+ * @param {number} [verbose=0]
45
+ */
46
+ constructor (
47
+ name = 'unnamed-test-runner',
48
+ type = RUNNER,
49
+ parent,
50
+ f,
51
+ recaller = testRunnerRecaller,
52
+ verbose = 0
53
+ ) {
54
+ this.parent = parent
55
+ this.name = name
56
+ this.type = type
57
+ this.#f = f
58
+ this.recaller = recaller
59
+ this.verbose = verbose
60
+ this.#runState = WAITING
61
+ this.assert = new Assert(this)
62
+ this.runIndex = 0
63
+ this.upserter = new TurtleDictionary(name, recaller)
64
+ }
65
+
66
+ async run () {
67
+ this.#runPromise ??= (async () => {
68
+ this.runState = RUNNING
69
+ try {
70
+ if (this.#f) await this.#f(this)
71
+ await this.runChildren()
72
+ } catch (error) {
73
+ this.error = error
74
+ if (this.verbose) logError(() => console.error(error))
75
+ if (!(error instanceof TestRunnerError)) {
76
+ logError(() => console.error(error))
77
+ this.caught(`caught error: ${error.message}`, () => { throw new TestRunnerError(`${this.name}.run`, { cause: error }) })
78
+ }
79
+ this.runState = FAILED
80
+ }
81
+ })()
82
+ return this.#runPromise
83
+ }
84
+
85
+ async runChildren () {
86
+ this.#runChildrenPromise ??= (async () => {
87
+ ++this.runIndex
88
+ const errors = []
89
+ const hasOnly = this.#children.some(child => child.type === ONLY)
90
+ for (const child of this.#children) {
91
+ if (hasOnly && child.type !== ONLY) child.runState = SKIPPED
92
+ if (child.type === SKIP) child.runState = SKIPPED
93
+ if (child.runState !== SKIPPED) await child.run()
94
+ if (child.runState === FAILED) errors.push(child.error)
95
+ }
96
+ if (errors.length) {
97
+ throw new TestRunnerError(`${this.name}.runChildren`, { cause: errors })
98
+ }
99
+ this.runState = PASSED
100
+ })()
101
+ return this.#runChildrenPromise
102
+ }
103
+
104
+ get status () {
105
+ const status = {
106
+ name: this.name,
107
+ type: this.type,
108
+ runState: this.runState,
109
+ children: this.children.map(child => child.status)
110
+ }
111
+ return status
112
+ }
113
+
114
+ get children () {
115
+ this.recaller.reportKeyAccess(this, 'children', 'get', this.name)
116
+ return this.#children
117
+ }
118
+
119
+ get runState () {
120
+ this.recaller.reportKeyAccess(this, 'runState', 'get', this.name)
121
+ return this.#runState
122
+ }
123
+
124
+ set runState (runState) {
125
+ this.recaller.reportKeyMutation(this, 'runState', 'get', this.name)
126
+ this.#runState = runState
127
+ }
128
+
129
+ /**
130
+ * @param {string} name
131
+ * @param {(runner: TestRunner) => any} f
132
+ * @param {string} type
133
+ * @returns {TestRunner}
134
+ */
135
+ appendChild (name, f, type) {
136
+ const child = new TestRunner(name, type, this, f, this.recaller, this.verbose)
137
+ this.#children.push(child)
138
+ this.#runChildrenPromise = undefined
139
+ this.recaller.reportKeyMutation(this, 'children', 'appendChild', this.name)
140
+ return child
141
+ }
142
+
143
+ clearChildren () {
144
+ this.#children = []
145
+ this.#runChildrenPromise = undefined
146
+ this.recaller.reportKeyMutation(this, 'children', 'clearChildren', this.name)
147
+ }
148
+
149
+ /**
150
+ * @param {string} name
151
+ * @param {(suite: TestRunner) => any} f
152
+ * @returns {TestRunner}
153
+ */
154
+ describe (name, f) {
155
+ return this.appendChild(name, f, SUITE)
156
+ }
157
+
158
+ /**
159
+ * @param {string} name
160
+ * @param {(test: TestRunner) => any} f
161
+ * @returns {TestRunner}
162
+ */
163
+ it (name, f) {
164
+ return this.appendChild(name, f, TEST)
165
+ }
166
+
167
+ /**
168
+ * @param {string} name
169
+ * @param {(test: TestRunner) => any} f
170
+ * @returns {TestRunner}
171
+ */
172
+ test (name, f) {
173
+ return this.appendChild(name, f, TEST)
174
+ }
175
+
176
+ /**
177
+ * @param {string} name
178
+ * @param {(test: TestRunner) => any} f
179
+ * @returns {TestRunner}
180
+ */
181
+ caught (name, f) {
182
+ return this.appendChild(name, f, ERROR)
183
+ }
184
+
185
+ /**
186
+ * @returns {TestRunner}
187
+ */
188
+ get only () {
189
+ return this.appendChild('only', () => {}, ONLY)
190
+ }
191
+
192
+ /**
193
+ * @returns {TestRunner}
194
+ */
195
+ get skip () {
196
+ return this.appendChild('skip', () => {}, SKIP)
197
+ }
198
+ }
199
+
200
+ export const globalTestRunner = new TestRunner('globalTestRunner')
@@ -0,0 +1,144 @@
1
+ import { ASSERTION, PASSED, RUNNER, SUITE, TEST, WAITING } from './TestRunnerConstants.js'
2
+ import { globalTestRunner, TestRunner, urlToName } from './TestRunner.js'
3
+
4
+ globalTestRunner.describe(urlToName(import.meta.url), suite => {
5
+ suite.it('waits for tests to complete', async test => {
6
+ const runner = new TestRunner()
7
+ let _suite
8
+ // runnerRecaller.watch('update status', () => {
9
+ // console.log(JSON.stringify(runner.status, undefined, 10))
10
+ // })
11
+ runner.describe('abc', suite => {
12
+ _suite = suite
13
+ suite.it('xy', async (assert) => {
14
+ await new Promise(resolve => setTimeout(resolve, 100))
15
+ assert.assert.equal(1, 1, '1 === 1')
16
+ await new Promise(resolve => setTimeout(resolve, 100))
17
+ assert.assert.equal(2, 2, '2 === 2')
18
+ })
19
+ suite.it('z', async (assert) => {
20
+ await new Promise(resolve => setTimeout(resolve, 100))
21
+ assert.assert.equal(3, 3, '3 === 3')
22
+ })
23
+ })
24
+
25
+ await runner.run()
26
+ test.assert.equal({
27
+ name: 'unnamed-test-runner',
28
+ type: RUNNER,
29
+ runState: PASSED,
30
+ children: [
31
+ {
32
+ name: 'abc',
33
+ type: SUITE,
34
+ runState: PASSED,
35
+ children: [
36
+ {
37
+ name: 'xy',
38
+ type: TEST,
39
+ runState: PASSED,
40
+ children: [
41
+ {
42
+ name: '1 === 1',
43
+ type: ASSERTION,
44
+ runState: PASSED,
45
+ children: []
46
+ },
47
+ {
48
+ name: '2 === 2',
49
+ type: ASSERTION,
50
+ runState: PASSED,
51
+ children: []
52
+ }
53
+ ]
54
+ },
55
+ {
56
+ name: 'z',
57
+ type: TEST,
58
+ runState: PASSED,
59
+ children: [
60
+ {
61
+ name: '3 === 3',
62
+ type: ASSERTION,
63
+ runState: PASSED,
64
+ children: []
65
+ }
66
+ ]
67
+ }
68
+ ]
69
+ }
70
+ ]
71
+ }, runner.status, 'after run')
72
+
73
+ _suite.it('m', async (assert) => {
74
+ await new Promise(resolve => setTimeout(resolve, 100))
75
+ assert.assert.equal(4, 5, '4 !== 5', false)
76
+ await new Promise(resolve => setTimeout(resolve, 100))
77
+ })
78
+
79
+ runner.describe('n', async (assert) => {
80
+ await new Promise(resolve => setTimeout(resolve, 100))
81
+ assert.assert.equal(5, 5, '5 === 5')
82
+ await new Promise(resolve => setTimeout(resolve, 100))
83
+ })
84
+
85
+ test.assert.equal({
86
+ name: 'unnamed-test-runner',
87
+ type: RUNNER,
88
+ runState: PASSED,
89
+ children: [
90
+ {
91
+ name: 'abc',
92
+ type: SUITE,
93
+ runState: PASSED,
94
+ children: [
95
+ {
96
+ name: 'xy',
97
+ type: TEST,
98
+ runState: PASSED,
99
+ children: [
100
+ {
101
+ name: '1 === 1',
102
+ type: ASSERTION,
103
+ runState: PASSED,
104
+ children: []
105
+ },
106
+ {
107
+ name: '2 === 2',
108
+ type: ASSERTION,
109
+ runState: PASSED,
110
+ children: []
111
+ }
112
+ ]
113
+ },
114
+ {
115
+ name: 'z',
116
+ type: TEST,
117
+ runState: PASSED,
118
+ children: [
119
+ {
120
+ name: '3 === 3',
121
+ type: ASSERTION,
122
+ runState: PASSED,
123
+ children: []
124
+ }
125
+ ]
126
+ },
127
+ {
128
+ name: 'm',
129
+ type: TEST,
130
+ runState: WAITING,
131
+ children: []
132
+ }
133
+ ]
134
+ },
135
+ {
136
+ name: 'n',
137
+ type: SUITE,
138
+ runState: WAITING,
139
+ children: []
140
+ }
141
+ ]
142
+ }, runner.status, 'after 2nd describe')
143
+ })
144
+ })
@@ -0,0 +1,13 @@
1
+ export const PASSED = '☑'
2
+ export const FAILED = '☒'
3
+ export const WAITING = '□'
4
+ export const RUNNING = '■'
5
+ export const SKIPPED = '⧄'
6
+
7
+ export const RUNNER = '߷'
8
+ export const SUITE = '●'
9
+ export const TEST = '⇶'
10
+ export const ASSERTION = '→'
11
+ export const ONLY = '⮕'
12
+ export const SKIP = '⇏'
13
+ export const ERROR = '⚠'
@@ -0,0 +1,18 @@
1
+ import { combineUint8Arrays } from './combineUint8Arrays.js'
2
+ import { logError } from './logger.js'
3
+
4
+ /**
5
+ * @param {Array.<any>} uint8ArrayLikes
6
+ * @returns {Uint8Array}
7
+ */
8
+ export function combineUint8ArrayLikes (uint8ArrayLikes) {
9
+ if (!Array.isArray(uint8ArrayLikes)) throw new Error('friendly reminder... combineUint8ArrayLikes accepts an array of Uint8ArrayLikes')
10
+ const uint8Arrays = uint8ArrayLikes.map(uint8ArrayLike => {
11
+ if (uint8ArrayLike instanceof Uint8Array) return uint8ArrayLike
12
+ if (uint8ArrayLike instanceof Object.getPrototypeOf(Uint8Array)) return new Uint8Array(uint8ArrayLike.buffer)
13
+ if (Number.isInteger(uint8ArrayLike) && uint8ArrayLike <= 0xff) return new Uint8Array([uint8ArrayLike])
14
+ logError(() => console.error(uint8ArrayLikes))
15
+ throw new Error('can\'t convert to Uint8Array')
16
+ })
17
+ return combineUint8Arrays(uint8Arrays)
18
+ }
@@ -0,0 +1,23 @@
1
+ import { logError } from './logger.js'
2
+
3
+ /**
4
+ * @param {Array.<Uint8Array>} uint8Arrays
5
+ * @returns {Uint8Array}
6
+ */
7
+ export function combineUint8Arrays (uint8Arrays) {
8
+ if (!Array.isArray(uint8Arrays)) throw new Error('friendly reminder... combineUint8Arrays accepts an array of Uint8Arrays')
9
+ const combinedLength = uint8Arrays.reduce((length, uint8Array) => length + (uint8Array?.length ?? 0), 0)
10
+ const collapsedUint8Array = new Uint8Array(combinedLength)
11
+ let address = 0
12
+ for (const uint8Array of uint8Arrays) {
13
+ if (!(uint8Array instanceof Uint8Array)) {
14
+ logError(() => console.error('not Uint8Array', uint8Array))
15
+ throw new Error('combineUint8Arrays can only combine Uint8Arrays')
16
+ }
17
+ if (uint8Array?.length) {
18
+ collapsedUint8Array.set(uint8Array, address)
19
+ address += uint8Array.length
20
+ }
21
+ }
22
+ return collapsedUint8Array
23
+ }