@weborigami/async-tree 0.5.8 → 0.6.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.
Files changed (182) hide show
  1. package/browser.js +1 -1
  2. package/index.ts +43 -35
  3. package/main.js +1 -2
  4. package/package.json +4 -7
  5. package/shared.js +74 -12
  6. package/src/Tree.js +11 -2
  7. package/src/drivers/AsyncMap.js +237 -0
  8. package/src/drivers/{BrowserFileTree.js → BrowserFileMap.js} +54 -38
  9. package/src/drivers/{calendarTree.js → CalendarMap.js} +80 -63
  10. package/src/drivers/ConstantMap.js +28 -0
  11. package/src/drivers/{ExplorableSiteTree.js → ExplorableSiteMap.js} +7 -7
  12. package/src/drivers/FileMap.js +238 -0
  13. package/src/drivers/{FunctionTree.js → FunctionMap.js} +19 -22
  14. package/src/drivers/ObjectMap.js +151 -0
  15. package/src/drivers/SetMap.js +13 -0
  16. package/src/drivers/{SiteTree.js → SiteMap.js} +17 -20
  17. package/src/drivers/SyncMap.js +260 -0
  18. package/src/jsonKeys.d.ts +2 -2
  19. package/src/jsonKeys.js +20 -5
  20. package/src/operations/addNextPrevious.js +35 -36
  21. package/src/operations/assign.js +27 -23
  22. package/src/operations/cache.js +30 -36
  23. package/src/operations/cachedKeyFunctions.js +1 -1
  24. package/src/operations/calendar.js +5 -0
  25. package/src/operations/child.js +35 -0
  26. package/src/operations/clear.js +13 -12
  27. package/src/operations/constant.js +5 -0
  28. package/src/operations/deepEntries.js +23 -0
  29. package/src/operations/deepMap.js +9 -9
  30. package/src/operations/deepMerge.js +36 -25
  31. package/src/operations/deepReverse.js +23 -16
  32. package/src/operations/deepTake.js +7 -7
  33. package/src/operations/deepText.js +4 -4
  34. package/src/operations/deepValues.js +3 -6
  35. package/src/operations/deepValuesIterator.js +11 -11
  36. package/src/operations/delete.js +8 -12
  37. package/src/operations/entries.js +17 -10
  38. package/src/operations/filter.js +9 -7
  39. package/src/operations/first.js +12 -10
  40. package/src/operations/forEach.js +10 -13
  41. package/src/operations/from.js +30 -39
  42. package/src/operations/globKeys.js +22 -17
  43. package/src/operations/group.js +2 -2
  44. package/src/operations/groupBy.js +24 -22
  45. package/src/operations/has.js +7 -9
  46. package/src/operations/indent.js +2 -2
  47. package/src/operations/inners.js +19 -15
  48. package/src/operations/invokeFunctions.js +22 -10
  49. package/src/operations/isAsyncMutableTree.js +5 -12
  50. package/src/operations/isAsyncTree.js +5 -20
  51. package/src/operations/isMap.js +39 -0
  52. package/src/operations/isMaplike.js +34 -0
  53. package/src/operations/isReadOnlyMap.js +14 -0
  54. package/src/operations/isTraversable.js +3 -3
  55. package/src/operations/isTreelike.js +5 -30
  56. package/src/operations/json.js +4 -12
  57. package/src/operations/keys.js +17 -8
  58. package/src/operations/length.js +9 -8
  59. package/src/operations/map.js +28 -30
  60. package/src/operations/mapExtension.js +20 -16
  61. package/src/operations/mapReduce.js +38 -24
  62. package/src/operations/mask.js +54 -29
  63. package/src/operations/match.js +13 -9
  64. package/src/operations/merge.js +43 -35
  65. package/src/operations/paginate.js +26 -18
  66. package/src/operations/parent.js +7 -7
  67. package/src/operations/paths.js +20 -22
  68. package/src/operations/plain.js +6 -6
  69. package/src/operations/reduce.js +16 -0
  70. package/src/operations/regExpKeys.js +21 -12
  71. package/src/operations/reverse.js +21 -15
  72. package/src/operations/root.js +6 -5
  73. package/src/operations/scope.js +31 -26
  74. package/src/operations/set.js +20 -0
  75. package/src/operations/shuffle.js +23 -16
  76. package/src/operations/size.js +13 -0
  77. package/src/operations/sort.js +55 -40
  78. package/src/operations/sync.js +14 -0
  79. package/src/operations/take.js +23 -11
  80. package/src/operations/text.js +4 -4
  81. package/src/operations/toFunction.js +7 -7
  82. package/src/operations/traverse.js +4 -4
  83. package/src/operations/traverseOrThrow.js +18 -9
  84. package/src/operations/traversePath.js +2 -2
  85. package/src/operations/values.js +18 -9
  86. package/src/operations/withKeys.js +22 -16
  87. package/src/symbols.js +1 -0
  88. package/src/utilities/castArraylike.js +24 -13
  89. package/src/utilities/getMapArgument.js +38 -0
  90. package/src/utilities/getParent.js +2 -2
  91. package/src/utilities/isStringlike.js +7 -5
  92. package/src/utilities/setParent.js +7 -7
  93. package/src/utilities/toFunction.js +2 -2
  94. package/src/utilities/toPlainValue.js +21 -19
  95. package/test/SampleAsyncMap.js +34 -0
  96. package/test/browser/assert.js +20 -0
  97. package/test/browser/index.html +53 -21
  98. package/test/drivers/AsyncMap.test.js +119 -0
  99. package/test/drivers/{BrowserFileTree.test.js → BrowserFileMap.test.js} +50 -33
  100. package/test/drivers/{calendarTree.test.js → CalendarMap.test.js} +17 -19
  101. package/test/drivers/ConstantMap.test.js +15 -0
  102. package/test/drivers/{ExplorableSiteTree.test.js → ExplorableSiteMap.test.js} +29 -14
  103. package/test/drivers/FileMap.test.js +156 -0
  104. package/test/drivers/FunctionMap.test.js +56 -0
  105. package/test/drivers/ObjectMap.test.js +194 -0
  106. package/test/drivers/SetMap.test.js +35 -0
  107. package/test/drivers/{SiteTree.test.js → SiteMap.test.js} +14 -10
  108. package/test/drivers/SyncMap.test.js +335 -0
  109. package/test/jsonKeys.test.js +18 -6
  110. package/test/operations/addNextPrevious.test.js +3 -2
  111. package/test/operations/assign.test.js +30 -35
  112. package/test/operations/cache.test.js +17 -12
  113. package/test/operations/cachedKeyFunctions.test.js +12 -9
  114. package/test/operations/child.test.js +34 -0
  115. package/test/operations/clear.test.js +6 -27
  116. package/test/operations/deepEntries.test.js +32 -0
  117. package/test/operations/deepMerge.test.js +23 -16
  118. package/test/operations/deepReverse.test.js +2 -2
  119. package/test/operations/deepTake.test.js +2 -2
  120. package/test/operations/deepText.test.js +4 -4
  121. package/test/operations/deepValuesIterator.test.js +2 -2
  122. package/test/operations/delete.test.js +2 -2
  123. package/test/operations/extensionKeyFunctions.test.js +6 -5
  124. package/test/operations/filter.test.js +3 -3
  125. package/test/operations/from.test.js +25 -31
  126. package/test/operations/globKeys.test.js +9 -9
  127. package/test/operations/groupBy.test.js +6 -5
  128. package/test/operations/inners.test.js +17 -14
  129. package/test/operations/invokeFunctions.test.js +2 -2
  130. package/test/operations/isMap.test.js +15 -0
  131. package/test/operations/isMaplike.test.js +15 -0
  132. package/test/operations/json.test.js +2 -2
  133. package/test/operations/keys.test.js +16 -3
  134. package/test/operations/map.test.js +40 -30
  135. package/test/operations/mapExtension.test.js +6 -6
  136. package/test/operations/mapReduce.test.js +14 -12
  137. package/test/operations/mask.test.js +16 -3
  138. package/test/operations/match.test.js +2 -2
  139. package/test/operations/merge.test.js +20 -14
  140. package/test/operations/paginate.test.js +5 -5
  141. package/test/operations/parent.test.js +3 -3
  142. package/test/operations/paths.test.js +20 -27
  143. package/test/operations/plain.test.js +8 -8
  144. package/test/operations/regExpKeys.test.js +22 -18
  145. package/test/operations/reverse.test.js +4 -3
  146. package/test/operations/scope.test.js +6 -5
  147. package/test/operations/set.test.js +11 -0
  148. package/test/operations/shuffle.test.js +3 -2
  149. package/test/operations/sort.test.js +7 -10
  150. package/test/operations/sync.test.js +43 -0
  151. package/test/operations/take.test.js +2 -2
  152. package/test/operations/toFunction.test.js +2 -2
  153. package/test/operations/traverse.test.js +17 -5
  154. package/test/operations/withKeys.test.js +2 -2
  155. package/test/utilities/castArrayLike.test.js +53 -0
  156. package/test/utilities/setParent.test.js +6 -6
  157. package/test/utilities/toFunction.test.js +2 -2
  158. package/test/utilities/toPlainValue.test.js +51 -12
  159. package/src/drivers/DeepMapTree.js +0 -23
  160. package/src/drivers/DeepObjectTree.js +0 -18
  161. package/src/drivers/DeferredTree.js +0 -81
  162. package/src/drivers/FileTree.js +0 -276
  163. package/src/drivers/MapTree.js +0 -70
  164. package/src/drivers/ObjectTree.js +0 -158
  165. package/src/drivers/SetTree.js +0 -34
  166. package/src/drivers/constantTree.js +0 -19
  167. package/src/drivers/limitConcurrency.js +0 -63
  168. package/src/internal.js +0 -16
  169. package/src/utilities/getTreeArgument.js +0 -43
  170. package/test/drivers/DeepMapTree.test.js +0 -17
  171. package/test/drivers/DeepObjectTree.test.js +0 -35
  172. package/test/drivers/DeferredTree.test.js +0 -22
  173. package/test/drivers/FileTree.test.js +0 -192
  174. package/test/drivers/FunctionTree.test.js +0 -46
  175. package/test/drivers/MapTree.test.js +0 -59
  176. package/test/drivers/ObjectTree.test.js +0 -163
  177. package/test/drivers/SetTree.test.js +0 -44
  178. package/test/drivers/constantTree.test.js +0 -13
  179. package/test/drivers/limitConcurrency.test.js +0 -41
  180. package/test/operations/isAsyncMutableTree.test.js +0 -17
  181. package/test/operations/isAsyncTree.test.js +0 -26
  182. package/test/operations/isTreelike.test.js +0 -13
@@ -1,21 +1,21 @@
1
- import isAsyncTree from "../operations/isAsyncTree.js";
1
+ import isMap from "../operations/isMap.js";
2
2
  import * as symbols from "../symbols.js";
3
3
 
4
4
  /**
5
5
  * If the child object doesn't have a parent yet, set it to the indicated
6
- * parent. If the child is an AsyncTree, set the `parent` property. Otherwise,
6
+ * parent. If the child is a map, set the `parent` property. Otherwise,
7
7
  * set the `symbols.parent` property.
8
8
  *
9
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ * @typedef {import("../../index.ts").SyncOrAsyncMap} SyncOrAsyncMap
10
10
  *
11
11
  * @param {*} child
12
- * @param {AsyncTree|null} parent
12
+ * @param {SyncOrAsyncMap|null} parent
13
13
  */
14
14
  export default function setParent(child, parent) {
15
- if (isAsyncTree(child)) {
15
+ if (isMap(child)) {
16
16
  // Value is a subtree; set its parent to this tree.
17
- if (!child.parent) {
18
- child.parent = parent;
17
+ if ("parent" in child && !child.parent) {
18
+ /** @type {any} */ (child).parent = parent;
19
19
  }
20
20
  } else if (Object.isExtensible(child) && !child[symbols.parent]) {
21
21
  try {
@@ -1,5 +1,5 @@
1
1
  import from from "../operations/from.js";
2
- import isTreelike from "../operations/isTreelike.js";
2
+ import isMaplike from "../operations/isMaplike.js";
3
3
  import isUnpackable from "./isUnpackable.js";
4
4
 
5
5
  /**
@@ -29,7 +29,7 @@ export default function toFunction(obj) {
29
29
  const fn = await fnPromise;
30
30
  return fn(...args);
31
31
  };
32
- } else if (isTreelike(obj)) {
32
+ } else if (isMaplike(obj)) {
33
33
  // Return a function that invokes the tree's getter.
34
34
  const tree = from(obj);
35
35
  return tree.get.bind(tree);
@@ -1,7 +1,6 @@
1
- import ObjectTree from "../drivers/ObjectTree.js";
2
- import isTreelike from "../operations/isTreelike.js";
1
+ import ObjectMap from "../drivers/ObjectMap.js";
2
+ import isMaplike from "../operations/isMaplike.js";
3
3
  import mapReduce from "../operations/mapReduce.js";
4
- import * as trailingSlash from "../trailingSlash.js";
5
4
  import castArraylike from "./castArraylike.js";
6
5
  import isPrimitive from "./isPrimitive.js";
7
6
  import isStringlike from "./isStringlike.js";
@@ -9,10 +8,8 @@ import toString from "./toString.js";
9
8
  import TypedArray from "./TypedArray.js";
10
9
 
11
10
  /**
12
- * Convert the given input to the plainest possible JavaScript value. This
13
- * helper is intended for functions that want to accept an argument from the ori
14
- * CLI, which could a string, a stream of data, or some other kind of JavaScript
15
- * object.
11
+ * Convert the given input to the plainest possible JavaScript value. This is
12
+ * useful for rendering data for display or serialization.
16
13
  *
17
14
  * If the input is a function, it will be invoked and its result will be
18
15
  * processed.
@@ -20,7 +17,7 @@ import TypedArray from "./TypedArray.js";
20
17
  * If the input is a promise, it will be resolved and its result will be
21
18
  * processed.
22
19
  *
23
- * If the input is treelike, it will be converted to a plain JavaScript object,
20
+ * If the input is maplike, it will be converted to a plain JavaScript object,
24
21
  * recursively traversing the tree and converting all values to plain types.
25
22
  *
26
23
  * If the input is stringlike, its text will be returned.
@@ -33,9 +30,13 @@ import TypedArray from "./TypedArray.js";
33
30
  * returned as a plain object.
34
31
  *
35
32
  * @param {any} input
33
+ * @param {import("../../index.ts").ReduceFn} [reduceFn]
36
34
  * @returns {Promise<any>}
37
35
  */
38
- export default async function toPlainValue(input) {
36
+ export default async function toPlainValue(
37
+ input,
38
+ reduceFn = reduceToPlainObject
39
+ ) {
39
40
  if (input instanceof Function) {
40
41
  // Invoke function
41
42
  input = input();
@@ -47,17 +48,9 @@ export default async function toPlainValue(input) {
47
48
 
48
49
  if (isPrimitive(input) || input instanceof Date) {
49
50
  return input;
50
- } else if (isTreelike(input)) {
51
+ } else if (isMaplike(input)) {
51
52
  // Recursively convert tree to plain object.
52
- return mapReduce(input, toPlainValue, (values, keys, tree) => {
53
- // Special case for an empty tree: if based on array, return array.
54
- if (tree instanceof ObjectTree && keys.length === 0) {
55
- return /** @type {any} */ (tree).object instanceof Array ? [] : {};
56
- }
57
- // Normalize slashes in keys.
58
- keys = keys.map(trailingSlash.remove);
59
- return castArraylike(keys, values);
60
- });
53
+ return mapReduce(input, (value) => toPlainValue(value, reduceFn), reduceFn);
61
54
  } else if (input instanceof ArrayBuffer || input instanceof TypedArray) {
62
55
  // Try to interpret the buffer as UTF-8 text, otherwise use base64.
63
56
  const text = toString(input);
@@ -78,6 +71,15 @@ export default async function toPlainValue(input) {
78
71
  }
79
72
  }
80
73
 
74
+ function reduceToPlainObject(mapped, source) {
75
+ // Normalize slashes in keys.
76
+ // Special case for an empty map: if based on array, return array.
77
+ if (mapped.size === 0 && source instanceof ObjectMap) {
78
+ return /** @type {any} */ (source).object instanceof Array ? [] : {};
79
+ }
80
+ return castArraylike(mapped);
81
+ }
82
+
81
83
  function toBase64(object) {
82
84
  if (typeof Buffer !== "undefined") {
83
85
  // Node.js environment
@@ -0,0 +1,34 @@
1
+ import AsyncMap from "../src/drivers/AsyncMap.js";
2
+ import * as trailingSlash from "../src/trailingSlash.js";
3
+
4
+ /**
5
+ * For testing AsyncMaps
6
+ */
7
+ export default class SampleAsyncMap extends AsyncMap {
8
+ constructor(entries) {
9
+ super();
10
+ this.map = new Map(entries);
11
+ }
12
+
13
+ async delete(key) {
14
+ const normalized = trailingSlash.remove(key);
15
+ return this.map.delete(normalized);
16
+ }
17
+
18
+ async get(key) {
19
+ let value = this.map.get(key);
20
+ if (value instanceof Array) {
21
+ value = Reflect.construct(this.constructor, [value]);
22
+ }
23
+ return value;
24
+ }
25
+
26
+ async *keys() {
27
+ yield* this.map.keys();
28
+ }
29
+
30
+ async set(key, value) {
31
+ this.map.set(key, value);
32
+ return this;
33
+ }
34
+ }
@@ -44,6 +44,10 @@ assert.deepEqual = (actual, expected) => {
44
44
  throw new Error(`Expected ${expected} but got ${actual}`);
45
45
  };
46
46
 
47
+ // For browser testing purposes we treat these the same
48
+ assert.strictEqual = assert.equal;
49
+ assert.deepStrictEqual = assert.deepEqual;
50
+
47
51
  assert.rejects = async (fn) => {
48
52
  try {
49
53
  await fn();
@@ -52,3 +56,19 @@ assert.rejects = async (fn) => {
52
56
  }
53
57
  throw new Error("Expected promise to reject but it resolved");
54
58
  };
59
+
60
+ assert.throws = (fn, expected) => {
61
+ try {
62
+ fn();
63
+ } catch (/** @type {any} */ error) {
64
+ if (expected) {
65
+ if (error.name !== expected.name || error.message !== expected.message) {
66
+ throw new Error(
67
+ `Expected error ${expected.name}: ${expected.message} but got ${error.name}: ${error.message}`
68
+ );
69
+ }
70
+ }
71
+ return;
72
+ }
73
+ throw new Error("Expected function to throw but it did not");
74
+ };
@@ -11,48 +11,80 @@
11
11
  }
12
12
  }
13
13
  </script>
14
- <!-- Omit FileTree.test.js, which is Node.js only -->
15
- <!-- Omit SiteTree.test.js, which requires mocks -->
14
+ <!-- Omit FileMap.test.js, which is Node.js only -->
15
+ <!-- Omit SiteMap.test.js, which requires mocks -->
16
16
 
17
- <!-- Unclear why drivers/constantTree.test.js won't load -->
18
-
19
- <script type="module" src="../Tree.test.js"></script>
20
- <script type="module" src="../drivers/BrowserFileTree.test.js"></script>
21
- <script type="module" src="../drivers/DeepMapTree.test.js"></script>
22
- <script type="module" src="../drivers/DeepObjectTree.test.js"></script>
23
- <script type="module" src="../drivers/DeferredTree.test.js"></script>
24
- <script type="module" src="../drivers/FunctionTree.test.js"></script>
25
- <script type="module" src="../drivers/MapTree.test.js"></script>
26
- <script type="module" src="../drivers/ObjectTree.test.js"></script>
27
- <script type="module" src="../drivers/SetTree.test.js"></script>
28
- <script type="module" src="../drivers/calendarTree.test.js"></script>
17
+ <script type="module" src="../drivers/AsyncMap.test.js"></script>
18
+ <script type="module" src="../drivers/BrowserFileMap.test.js"></script>
19
+ <script type="module" src="../drivers/CalendarMap.test.js"></script>
20
+ <script type="module" src="../drivers/ConstantMap.test.js"></script>
21
+ <script type="module" src="../drivers/FunctionMap.test.js"></script>
22
+ <script type="module" src="../drivers/ObjectMap.test.js"></script>
23
+ <script type="module" src="../drivers/SetMap.test.js"></script>
24
+ <script type="module" src="../drivers/SyncMap.test.js"></script>
29
25
  <script type="module" src="../operations/addNextPrevious.test.js"></script>
26
+ <script type="module" src="../operations/assign.test.js"></script>
30
27
  <script type="module" src="../operations/cache.test.js"></script>
31
- <script type="module" src="../operations/cache.test.js"></script>
32
- <script type="module" src="../operations/cachedKeyFunctions.test.js"></script>
33
- <script type="module" src="../operations/concat.test.js"></script>
28
+ <script
29
+ type="module"
30
+ src="../operations/cachedKeyFunctions.test.js"
31
+ ></script>
32
+ <script type="module" src="../operations/clear.test.js"></script>
33
+ <script type="module" src="../operations/deepEntries.test.js"></script>
34
34
  <script type="module" src="../operations/deepMerge.test.js"></script>
35
35
  <script type="module" src="../operations/deepReverse.test.js"></script>
36
36
  <script type="module" src="../operations/deepTake.test.js"></script>
37
37
  <script type="module" src="../operations/deepText.test.js"></script>
38
38
  <script type="module" src="../operations/deepValues.test.js"></script>
39
- <script type="module" src="../operations/deepValuesIterator.test.js"></script>
40
- <script type="module" src="../operations/extensionKeyFunctions.test.js"></script>
39
+ <script
40
+ type="module"
41
+ src="../operations/deepValuesIterator.test.js"
42
+ ></script>
43
+ <script type="module" src="../operations/delete.test.js"></script>
44
+ <script type="module" src="../operations/entries.test.js"></script>
45
+ <script
46
+ type="module"
47
+ src="../operations/extensionKeyFunctions.test.js"
48
+ ></script>
41
49
  <script type="module" src="../operations/filter.test.js"></script>
50
+ <script type="module" src="../operations/first.test.js"></script>
51
+ <script type="module" src="../operations/forEach.test.js"></script>
52
+ <script type="module" src="../operations/from.test.js"></script>
42
53
  <script type="module" src="../operations/globKeys.test.js"></script>
43
- <script type="module" src="../operations/group.test.js"></script>
54
+ <script type="module" src="../operations/groupBy.test.js"></script>
55
+ <script type="module" src="../operations/groupBy.test.js"></script>
56
+ <script type="module" src="../operations/has.test.js"></script>
57
+ <script type="module" src="../operations/indent.test.js"></script>
58
+ <script type="module" src="../operations/inners.test.js"></script>
44
59
  <script type="module" src="../operations/invokeFunctions.test.js"></script>
60
+ <script type="module" src="../operations/isMap.test.js"></script>
61
+ <script type="module" src="../operations/isMaplike.test.js"></script>
62
+ <script type="module" src="../operations/json.test.js"></script>
63
+ <script type="module" src="../operations/keys.test.js"></script>
64
+ <script type="module" src="../operations/length.test.js"></script>
45
65
  <script type="module" src="../operations/map.test.js"></script>
66
+ <script type="module" src="../operations/mapExtension.test.js"></script>
67
+ <script type="module" src="../operations/mapReduce.test.js"></script>
46
68
  <script type="module" src="../operations/mask.test.js"></script>
69
+ <script type="module" src="../operations/match.test.js"></script>
47
70
  <script type="module" src="../operations/merge.test.js"></script>
48
71
  <script type="module" src="../operations/paginate.test.js"></script>
49
72
  <script type="module" src="../operations/parseExtensions.test.js"></script>
73
+ <script type="module" src="../operations/paths.test.js"></script>
74
+ <script type="module" src="../operations/plain.test.js"></script>
50
75
  <script type="module" src="../operations/regExpKeys.test.js"></script>
51
76
  <script type="module" src="../operations/reverse.test.js"></script>
52
77
  <script type="module" src="../operations/scope.test.js"></script>
78
+ <script type="module" src="../operations/shuffle.test.js"></script>
53
79
  <script type="module" src="../operations/sort.test.js"></script>
80
+ <script type="module" src="../operations/sync.test.js"></script>
54
81
  <script type="module" src="../operations/take.test.js"></script>
55
- <script type="module" src="../utilities.test.js"></script>
82
+ <script type="module" src="../operations/text.test.js"></script>
83
+ <script type="module" src="../operations/toFunction.test.js"></script>
84
+ <script type="module" src="../operations/traverse.test.js"></script>
85
+ <script type="module" src="../operations/traversePath.test.js"></script>
86
+ <script type="module" src="../operations/values.test.js"></script>
87
+ <script type="module" src="../operations/withKeys.test.js"></script>
56
88
  </head>
57
89
  <body></body>
58
90
  </html>
@@ -0,0 +1,119 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import AsyncMap from "../../src/drivers/AsyncMap.js";
4
+ import SampleAsyncMap from "../SampleAsyncMap.js";
5
+
6
+ describe("AsyncMap", () => {
7
+ test("clear", async () => {
8
+ const map = new SampleAsyncMap([
9
+ ["a", 1],
10
+ ["b", 2],
11
+ ]);
12
+ assert.strictEqual(await map.size, 2);
13
+ await map.clear();
14
+ assert.strictEqual(await map.size, 0);
15
+ });
16
+
17
+ test("entries invokes keys() and get()", async () => {
18
+ const map = new SampleAsyncMap([
19
+ ["a", 1],
20
+ ["b", 2],
21
+ ]);
22
+ const entries = [];
23
+ for await (const entry of map.entries()) {
24
+ entries.push(entry);
25
+ }
26
+ assert.deepStrictEqual(entries, [
27
+ ["a", 1],
28
+ ["b", 2],
29
+ ]);
30
+ });
31
+
32
+ test("forEach", async () => {
33
+ const map = new SampleAsyncMap([
34
+ ["a", 1],
35
+ ["b", 2],
36
+ ]);
37
+ const calls = [];
38
+ await map.forEach(async (value, key, theMap) => {
39
+ calls.push([key, value, theMap]);
40
+ });
41
+ assert.deepStrictEqual(calls, [
42
+ ["a", 1, map],
43
+ ["b", 2, map],
44
+ ]);
45
+ });
46
+
47
+ test("static groupBy", async () => {
48
+ const items = [
49
+ { name: "apple", type: "fruit" },
50
+ { name: "beet", type: "vegetable" },
51
+ { name: "cherry", type: "fruit" },
52
+ ];
53
+ const map = await AsyncMap.groupBy(
54
+ items,
55
+ async (element, index) => element.type
56
+ );
57
+ assert.deepStrictEqual(Array.from(map.entries()), [
58
+ [
59
+ "fruit",
60
+ [
61
+ { name: "apple", type: "fruit" },
62
+ { name: "cherry", type: "fruit" },
63
+ ],
64
+ ],
65
+ ["vegetable", [{ name: "beet", type: "vegetable" }]],
66
+ ]);
67
+ });
68
+
69
+ test("has returns true if key exists in keys()", async () => {
70
+ const map = new AsyncMap();
71
+ map.keys = async function* () {
72
+ yield* ["a", "b/"];
73
+ };
74
+ assert.strictEqual(await map.has("a"), true);
75
+ assert.strictEqual(await map.has("b"), true); // trailing slash optional
76
+ assert.strictEqual(await map.has("b/"), true);
77
+ assert.strictEqual(await map.has("c"), false);
78
+ });
79
+
80
+ test("readOnly if get() is overridden but not delete() and set()", () => {
81
+ const map1 = new AsyncMap();
82
+ assert.strictEqual(map1.readOnly, false);
83
+
84
+ const map2 = new AsyncMap();
85
+ map2.get = async (key) => null;
86
+ assert.strictEqual(map2.readOnly, true);
87
+
88
+ const map3 = new AsyncMap();
89
+ map3.delete = async (key) => false;
90
+ map3.get = async (key) => null;
91
+ map3.set = async (key, value) => map3;
92
+ assert.strictEqual(map3.readOnly, false);
93
+ });
94
+
95
+ test("size", async () => {
96
+ const map = new SampleAsyncMap();
97
+ assert.strictEqual(await map.size, 0);
98
+ await map.set("a", 1);
99
+ assert.strictEqual(await map.size, 1);
100
+ await map.set("b", 2);
101
+ assert.strictEqual(await map.size, 2);
102
+ await map.delete("a");
103
+ assert.strictEqual(await map.size, 1);
104
+ await map.clear();
105
+ assert.strictEqual(await map.size, 0);
106
+ });
107
+
108
+ test("values", async () => {
109
+ const map = new SampleAsyncMap([
110
+ ["a", 1],
111
+ ["b", 2],
112
+ ]);
113
+ const values = [];
114
+ for await (const value of map.values()) {
115
+ values.push(value);
116
+ }
117
+ assert.deepStrictEqual(values, [1, 2]);
118
+ });
119
+ });
@@ -1,15 +1,17 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import BrowserFileTree from "../../src/drivers/BrowserFileTree.js";
4
- import { Tree } from "../../src/internal.js";
3
+ import BrowserFileMap from "../../src/drivers/BrowserFileMap.js";
4
+ import keys from "../../src/operations/keys.js";
5
+ import map from "../../src/operations/map.js";
6
+ import plain from "../../src/operations/plain.js";
5
7
 
6
8
  // Skip these tests if we're not in a browser.
7
9
  const isBrowser = typeof window !== "undefined";
8
10
  if (isBrowser) {
9
- describe("BrowserFileTree", async () => {
11
+ describe("BrowserFileMap", async () => {
10
12
  test("can get the keys of the tree", async () => {
11
13
  const fixture = await createFixture();
12
- assert.deepEqual(Array.from(await fixture.keys()), [
14
+ assert.deepEqual(await keys(fixture), [
13
15
  "Alice.md",
14
16
  "Bob.md",
15
17
  "Carol.md",
@@ -57,6 +59,24 @@ if (isBrowser) {
57
59
  assert(await fixture.get("subfolder/"));
58
60
  });
59
61
 
62
+ test("can delete a value", async () => {
63
+ const fixture = await createFixture();
64
+
65
+ // Delete existing key.
66
+ const deleted = await fixture.delete("Bob.md");
67
+ assert.equal(deleted, true);
68
+
69
+ // Delete non-existing key.
70
+ const deletedAgain = await fixture.delete("Bob.md");
71
+ assert.equal(deletedAgain, false);
72
+
73
+ assert.deepEqual(await strings(fixture), {
74
+ "Alice.md": "Hello, **Alice**.",
75
+ "Carol.md": "Hello, **Carol**.",
76
+ subfolder: {},
77
+ });
78
+ });
79
+
60
80
  test("can set a value", async () => {
61
81
  const fixture = await createFixture();
62
82
 
@@ -66,41 +86,38 @@ if (isBrowser) {
66
86
  // New key.
67
87
  await fixture.set("David.md", "Hello, **David**.");
68
88
 
69
- // Delete key.
70
- await fixture.set("Bob.md", undefined);
71
-
72
- // Delete non-existent key.
73
- await fixture.set("xyz", undefined);
74
-
75
89
  assert.deepEqual(await strings(fixture), {
76
90
  "Alice.md": "Goodbye, **Alice**.",
91
+ "Bob.md": "Hello, **Bob**.",
77
92
  "Carol.md": "Hello, **Carol**.",
78
93
  "David.md": "Hello, **David**.",
79
94
  subfolder: {},
80
95
  });
81
96
  });
82
97
 
83
- test("can create a subfolder via set", async () => {
98
+ test("can return or create a subfolder via child", async () => {
84
99
  const fixture = await createFixture();
85
- const tree = {
86
- async get(key) {
87
- const name = key.replace(/\.md$/, "");
88
- return `Hello, **${name}**.`;
89
- },
90
- async keys() {
91
- return ["Ellen.md"];
92
- },
93
- };
94
- await fixture.set("more", tree);
95
- assert.deepEqual(await strings(fixture), {
96
- "Alice.md": "Hello, **Alice**.",
97
- "Bob.md": "Hello, **Bob**.",
98
- "Carol.md": "Hello, **Carol**.",
99
- more: {
100
- "Ellen.md": "Hello, **Ellen**.",
101
- },
102
- subfolder: {},
103
- });
100
+
101
+ // Return existing subfolder
102
+ const subfolder = await fixture.get("subfolder");
103
+ const child = await fixture.child("subfolder");
104
+ assert.equal(child, subfolder);
105
+
106
+ // Create new subfolder
107
+ const newChild = await fixture.child("newSubfolder");
108
+ assert.equal(newChild, subfolder);
109
+
110
+ // Replace existing file with folder
111
+ const replacedChild = await fixture.child("Alice.md");
112
+ assert.equal(replacedChild.parent, fixture);
113
+
114
+ assert.deepEqual(await keys(fixture), [
115
+ "Alice.md/",
116
+ "Bob.md",
117
+ "Carol.md",
118
+ "subfolder/",
119
+ "newSubfolder/",
120
+ ]);
104
121
  });
105
122
  });
106
123
  }
@@ -141,12 +158,12 @@ async function createFixture() {
141
158
  create: true,
142
159
  });
143
160
 
144
- return new BrowserFileTree(subdirectory);
161
+ return new BrowserFileMap(subdirectory);
145
162
  }
146
163
 
147
164
  async function strings(tree) {
148
- return Tree.plain(
149
- Tree.map(tree, {
165
+ return plain(
166
+ await map(tree, {
150
167
  deep: true,
151
168
  value: (value) => text(value),
152
169
  })
@@ -1,14 +1,12 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import calendar from "../../src/drivers/calendarTree.js";
3
+ import CalendarMap from "../../src/drivers/CalendarMap.js";
4
4
  import toPlainValue from "../../src/utilities/toPlainValue.js";
5
5
 
6
- describe("calendarTree", () => {
7
- test("without a start or end, returns a tree for today", async () => {
8
- const tree = calendar({
9
- value: (year, month, day) => `${year}-${month}-${day}`,
10
- });
11
- const plain = await toPlainValue(tree);
6
+ describe("CalendarMap", () => {
7
+ test("without a start or end, returns a map for today", async () => {
8
+ const map = new CalendarMap();
9
+ const plain = await toPlainValue(map);
12
10
  const today = new Date();
13
11
  const year = today.getFullYear();
14
12
  const month = (today.getMonth() + 1).toString().padStart(2, "0");
@@ -22,13 +20,12 @@ describe("calendarTree", () => {
22
20
  });
23
21
  });
24
22
 
25
- test("returns a tree for a month range", async () => {
26
- const tree = calendar({
23
+ test("returns a map for a month range", async () => {
24
+ const map = new CalendarMap({
27
25
  start: "2025-01",
28
26
  end: "2025-02",
29
- value: (year, month, day) => `${year}-${month}-${day}`,
30
27
  });
31
- const plain = await toPlainValue(tree);
28
+ const plain = await toPlainValue(map);
32
29
  assert.deepEqual(plain, {
33
30
  2025: {
34
31
  "01": {
@@ -98,22 +95,23 @@ describe("calendarTree", () => {
98
95
  });
99
96
  });
100
97
 
101
- test("returns a tree for a day range", async () => {
102
- const tree = calendar({
98
+ test("returns a map for a day range", async () => {
99
+ const map = new CalendarMap({
103
100
  start: "2025-02-27",
104
101
  end: "2025-03-02",
105
- value: (year, month, day) => `${year}-${month}-${day}`,
102
+ // Exercise custom value function
103
+ value: (year, month, day) => `${year}.${month}.${day}`,
106
104
  });
107
- const plain = await toPlainValue(tree);
105
+ const plain = await toPlainValue(map);
108
106
  assert.deepEqual(plain, {
109
107
  2025: {
110
108
  "02": {
111
- 27: "2025-02-27",
112
- 28: "2025-02-28",
109
+ 27: "2025.02.27",
110
+ 28: "2025.02.28",
113
111
  },
114
112
  "03": {
115
- "01": "2025-03-01",
116
- "02": "2025-03-02",
113
+ "01": "2025.03.01",
114
+ "02": "2025.03.02",
117
115
  },
118
116
  },
119
117
  });
@@ -0,0 +1,15 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import ConstantMap from "../../src/drivers/ConstantMap.js";
4
+ import keys from "../../src/operations/keys.js";
5
+ import traverse from "../../src/operations/traverse.js";
6
+
7
+ describe("ConstantMap", () => {
8
+ test("returns a deep tree that returns constant for all keys", async () => {
9
+ const fixture = new ConstantMap(1);
10
+ assert.deepEqual(Array.from(await keys(fixture)), []);
11
+ assert.equal(fixture.get("a"), 1);
12
+ assert.equal(fixture.get("b"), 1);
13
+ assert.equal(await traverse(fixture, "c/", "d/", "e"), 1);
14
+ });
15
+ });