@interactors/core 1.0.0-rc1.2 → 1.0.0-rc1.4

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 (72) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/cjs/constructor.js +62 -126
  3. package/dist/cjs/constructor.js.map +1 -1
  4. package/dist/cjs/converge.js +3 -5
  5. package/dist/cjs/converge.js.map +1 -1
  6. package/dist/cjs/format.js +50 -0
  7. package/dist/cjs/format.js.map +1 -0
  8. package/dist/cjs/index.js.map +1 -1
  9. package/dist/cjs/inspector.js +2 -1
  10. package/dist/cjs/inspector.js.map +1 -1
  11. package/dist/cjs/interaction.js +59 -35
  12. package/dist/cjs/interaction.js.map +1 -1
  13. package/dist/cjs/match.js +17 -7
  14. package/dist/cjs/match.js.map +1 -1
  15. package/dist/cjs/resolvers.js +98 -0
  16. package/dist/cjs/resolvers.js.map +1 -0
  17. package/dist/cjs/serialize.js +48 -0
  18. package/dist/cjs/serialize.js.map +1 -0
  19. package/dist/constructor.d.ts +0 -6
  20. package/dist/constructor.d.ts.map +1 -1
  21. package/dist/converge.d.ts +2 -1
  22. package/dist/converge.d.ts.map +1 -1
  23. package/dist/esm/constructor.js +59 -117
  24. package/dist/esm/constructor.js.map +1 -1
  25. package/dist/esm/converge.js +3 -5
  26. package/dist/esm/converge.js.map +1 -1
  27. package/dist/esm/{format-table.js → format.js} +19 -1
  28. package/dist/esm/format.js.map +1 -0
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/inspector.js +2 -1
  31. package/dist/esm/inspector.js.map +1 -1
  32. package/dist/esm/interaction.js +56 -29
  33. package/dist/esm/interaction.js.map +1 -1
  34. package/dist/esm/match.js +17 -7
  35. package/dist/esm/match.js.map +1 -1
  36. package/dist/esm/resolvers.js +88 -0
  37. package/dist/esm/resolvers.js.map +1 -0
  38. package/dist/esm/serialize.js +43 -0
  39. package/dist/esm/serialize.js.map +1 -0
  40. package/dist/format.d.ts +10 -0
  41. package/dist/format.d.ts.map +1 -0
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/inspector.d.ts.map +1 -1
  45. package/dist/interaction.d.ts +38 -10
  46. package/dist/interaction.d.ts.map +1 -1
  47. package/dist/match.d.ts +5 -5
  48. package/dist/match.d.ts.map +1 -1
  49. package/dist/resolvers.d.ts +9 -0
  50. package/dist/resolvers.d.ts.map +1 -0
  51. package/dist/serialize.d.ts +13 -0
  52. package/dist/serialize.d.ts.map +1 -0
  53. package/dist/specification.d.ts +14 -11
  54. package/dist/specification.d.ts.map +1 -1
  55. package/dist/tsconfig.build.tsbuildinfo +1 -1
  56. package/package.json +4 -3
  57. package/src/constructor.ts +65 -133
  58. package/src/converge.ts +3 -6
  59. package/src/format.ts +58 -0
  60. package/src/index.ts +1 -1
  61. package/src/inspector.ts +2 -1
  62. package/src/interaction.ts +101 -36
  63. package/src/match.ts +23 -15
  64. package/src/resolvers.ts +100 -0
  65. package/src/serialize.ts +54 -0
  66. package/src/specification.ts +16 -13
  67. package/dist/cjs/format-table.js +0 -30
  68. package/dist/cjs/format-table.js.map +0 -1
  69. package/dist/esm/format-table.js.map +0 -1
  70. package/dist/format-table.d.ts +0 -6
  71. package/dist/format-table.d.ts.map +0 -1
  72. package/src/format-table.ts +0 -34
@@ -0,0 +1,100 @@
1
+
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+
4
+ import { globals } from '@interactors/globals';
5
+ import { Match } from './match';
6
+ import { NoSuchElementError, NotAbsentError, AmbiguousElementError } from './errors';
7
+ import { formatDescription, formatMatchesTable } from './format';
8
+ import { InteractorOptions } from './specification';
9
+
10
+ const defaultSelector = 'div';
11
+
12
+ export function findElements<E extends Element>(parentElement: Element, interactor: InteractorOptions<any, any, any>): E[] {
13
+ if (!interactor.name) {
14
+ throw new Error('One of your interactors was created without a name. Please provide a label for your interactor:\n\tHTML.extend(\'my interactor\') || createInteractor(\'my interactor\')');
15
+ }
16
+ if (typeof interactor.specification.selector === 'function') {
17
+ return interactor.specification.selector(parentElement);
18
+ } else if (interactor.specification.selector === ':root') {
19
+ // this is a bit of a hack, because otherwise there isn't a good way of selecting the root element
20
+ return [parentElement.ownerDocument.querySelector(':root') as E];
21
+ } else {
22
+ return Array.from(parentElement.querySelectorAll(interactor.specification.selector || defaultSelector));
23
+ }
24
+ }
25
+
26
+ function findMatches(parentElement: Element, interactor: InteractorOptions<any, any, any>): Match<Element, any>[] {
27
+ return findElements(parentElement, interactor).map((e) => new Match(e, interactor.filter, interactor.locator));
28
+ }
29
+
30
+ function throwNoSuchElementError(matches: Match<Element, any>[], interactor: InteractorOptions<any, any, any>): never {
31
+ if (matches.length === 0) {
32
+ throw new NoSuchElementError(`did not find ${formatDescription(interactor)}`);
33
+ } else {
34
+ throw new NoSuchElementError(`did not find ${formatDescription(interactor)}, did you mean one of:\n\n${formatMatchesTable(interactor, matches)}`);
35
+ }
36
+ }
37
+
38
+ function findFirstMatch(parentElement: Element, interactor: InteractorOptions<any, any, any>): Match<Element, any> {
39
+ let matches: Match<Element, any>[] = []
40
+ for (let element of findElements(parentElement, interactor)) {
41
+ let match = new Match(element, interactor.filter, interactor.locator)
42
+ matches.push(match)
43
+ if (match.matches) {
44
+ return match
45
+ }
46
+ }
47
+ throwNoSuchElementError(matches, interactor);
48
+ }
49
+
50
+ export function hasMatchMatching(parentElement: Element, interactor: InteractorOptions<any, any, any>): boolean {
51
+ return findElements(parentElement, interactor).some(element => new Match(element, interactor.filter, interactor.locator).matches);
52
+ }
53
+
54
+ function findMatchesNonEmpty(parentElement: Element, interactor: InteractorOptions<any, any, any>): Match<Element, any>[] {
55
+ let matches = findMatches(parentElement, interactor);
56
+ let matching = matches.filter((m) => m.matches);
57
+ if (matching.length > 0) {
58
+ return matching;
59
+ }
60
+ throwNoSuchElementError(matches, interactor);
61
+ }
62
+
63
+ // Given a parent element, and an interactor, find exactly one matching element
64
+ // and return it. If no elements match, raise an error. If more than one
65
+ // element matches, raise an error.
66
+ export function resolveUnique(parentElement: Element, interactor: InteractorOptions<any, any, any>): Element {
67
+ let matching = findMatchesNonEmpty(parentElement, interactor);
68
+
69
+ if (matching.length === 1) {
70
+ return matching[0].element;
71
+ } else {
72
+ let alternatives = matching.map((m) => '- ' + m.elementDescription());
73
+ throw new AmbiguousElementError(`${formatDescription(interactor)} matches multiple elements:\n\n${alternatives.join('\n')}`);
74
+ }
75
+ }
76
+
77
+ // Given a parent element, and an interactor, find the first matching element and
78
+ // return it. If no elements match, raise an error.
79
+ export function resolveFirst(parentElement: Element, interactor: InteractorOptions<any, any, any>): Element {
80
+ return findFirstMatch(parentElement, interactor).element
81
+ }
82
+
83
+ // Given a parent element, and an interactor, check if there are any matching
84
+ // elements, and throw an error if there are. Otherwise return undefined.
85
+ export function resolveEmpty(parentElement: Element, interactor: InteractorOptions<any, any, any>): void {
86
+ let matching = findMatches(parentElement, interactor).filter((m) => m.matches)
87
+
88
+ if (matching.length !== 0) {
89
+ let alternatives = matching.map((m) => '- ' + m.elementDescription());
90
+ throw new NotAbsentError(`${formatDescription(interactor)} exists but should not:\n\n${alternatives.join('\n')}`);
91
+ }
92
+ }
93
+
94
+ export function unsafeSyncResolveParent(options: InteractorOptions<any, any, any>): Element {
95
+ return options.ancestors.reduce(resolveUnique, globals.document.documentElement);
96
+ }
97
+
98
+ export function unsafeSyncResolveUnique<E extends Element>(options: InteractorOptions<E, any, any>): E {
99
+ return resolveUnique(unsafeSyncResolveParent(options), options) as E;
100
+ }
@@ -0,0 +1,54 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { InteractionOptions as SerializedInteractionOptions, InteractionType } from "@interactors/globals";
3
+ import { pascalCase } from "change-case";
4
+ import { InteractionOptions } from "./interaction";
5
+ import { matcherCode } from "./matcher";
6
+ import { InteractorOptions } from "./specification";
7
+
8
+ export function serializeInteractorOptions(options: InteractorOptions<any, any, any>): {
9
+ interactor: string;
10
+ filter: { [name: string]: unknown };
11
+ locator: string;
12
+ code: () => string;
13
+ } {
14
+ let locator = matcherCode(options.locator?.value);
15
+ return {
16
+ interactor: options.name,
17
+ filter: options.filter.all,
18
+ locator,
19
+ code() {
20
+ let interactorName = pascalCase(options.name);
21
+ let filters = Object.entries(options.filter.all).map(([name, filter]) => `"${name}": ${matcherCode(filter)}`);
22
+ return `${interactorName}(${[locator, filters.length && `{ ${filters.join(", ")} }`]
23
+ .filter(Boolean)
24
+ .join(", ")})`;
25
+ },
26
+ };
27
+ }
28
+
29
+ export function serializeInteractionOptions<E extends Element, T>(
30
+ type: InteractionType,
31
+ { name, interactor: { options }, args, filters }: InteractionOptions<E, T>
32
+ ): SerializedInteractionOptions {
33
+ let interactor = serializeInteractorOptions(options);
34
+ let ancestors = options.ancestors.map((ancestor) => serializeInteractorOptions(ancestor));
35
+ return {
36
+ ...interactor,
37
+ ancestors,
38
+ name,
39
+ type,
40
+ args,
41
+ code() {
42
+ let serializedArgs = "";
43
+ if (filters) {
44
+ let serializedFilters = Object.entries(filters).map(([name, filter]) => `"${name}": ${matcherCode(filter)}`);
45
+ serializedArgs = `{ ${serializedFilters.join(", ")} }`;
46
+ } else if (args) {
47
+ serializedArgs = args.map((arg) => JSON.stringify(arg)).join(", ");
48
+ }
49
+ return `${[...ancestors, interactor]
50
+ .map(({ code }, index) => (index == 0 ? code() : `${code()})`))
51
+ .join(".find(")}.${name}(${serializedArgs})`;
52
+ },
53
+ };
54
+ }
@@ -1,8 +1,9 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
 
3
+ import { Operation } from '@effection/core';
3
4
  import { FilterSet } from './filter-set';
4
5
  import { Locator } from './locator';
5
- import { Interaction, ReadonlyInteraction } from './interaction';
6
+ import { ActionInteraction, AssertionInteraction } from './interaction';
6
7
  import { MergeObjects } from './merge-objects';
7
8
  import { MaybeMatcher } from './matcher';
8
9
 
@@ -38,7 +39,7 @@ export interface Interactor<E extends Element, F extends FilterParams<any, any>>
38
39
  * await Link('Next').perform((e) => e.click());
39
40
  * ```
40
41
  */
41
- perform(fn: (element: E) => void): Interaction<void>;
42
+ perform(fn: (element: E) => void): ActionInteraction<E, void>;
42
43
 
43
44
  /**
44
45
  * Perform a one-off assertion on the given interactor. Takes a function which
@@ -54,7 +55,7 @@ export interface Interactor<E extends Element, F extends FilterParams<any, any>>
54
55
  * await Link('Next').assert((e) => assert(e.tagName === 'A'));
55
56
  * ```
56
57
  */
57
- assert(fn: (element: E) => void): ReadonlyInteraction<void>;
58
+ assert(fn: (element: E) => void): AssertionInteraction<E, void>;
58
59
 
59
60
  /**
60
61
  * An assertion which checks that an element matching the interactor exists.
@@ -66,7 +67,7 @@ export interface Interactor<E extends Element, F extends FilterParams<any, any>>
66
67
  * await Link('Next').exists();
67
68
  * ```
68
69
  */
69
- exists(): ReadonlyInteraction<void> & FilterObject<boolean, Element>;
70
+ exists(): AssertionInteraction<E, void> & FilterObject<boolean, Element>;
70
71
 
71
72
  /**
72
73
  * An assertion which checks that an element matching the interactor does not
@@ -78,7 +79,7 @@ export interface Interactor<E extends Element, F extends FilterParams<any, any>>
78
79
  * await Link('Next').absent();
79
80
  * ```
80
81
  */
81
- absent(): ReadonlyInteraction<void> & FilterObject<boolean, Element>;
82
+ absent(): AssertionInteraction<E, void> & FilterObject<boolean, Element>;
82
83
 
83
84
  /**
84
85
  * Checks that there is one element matching the interactor, and that this
@@ -91,7 +92,7 @@ export interface Interactor<E extends Element, F extends FilterParams<any, any>>
91
92
  * await Link('Home').has({ href: '/' })
92
93
  * ```
93
94
  */
94
- has(filters: F): ReadonlyInteraction<void>;
95
+ has(filters: F): AssertionInteraction<E, void>;
95
96
 
96
97
  /**
97
98
  * Identical to {@link has}, but reads better with some filters.
@@ -102,7 +103,7 @@ export interface Interactor<E extends Element, F extends FilterParams<any, any>>
102
103
  * await CheckBox('Accept conditions').is({ checked: true })
103
104
  * ```
104
105
  */
105
- is(filters: F): ReadonlyInteraction<void>;
106
+ is(filters: F): AssertionInteraction<E, void>;
106
107
 
107
108
  /**
108
109
  * Returns a copy of the given interactor which is scoped to this interactor.
@@ -128,7 +129,7 @@ export interface Interactor<E extends Element, F extends FilterParams<any, any>>
128
129
  apply: FilterFn<string, Element>;
129
130
  }
130
131
 
131
- export type ActionFn<E extends Element, I extends Interactor<E, any>> = (interactor: I, ...args: any[]) => Promise<unknown>;
132
+ export type ActionFn<E extends Element, I extends Interactor<E, any>> = (interactor: I, ...args: any[]) => Operation<unknown>;
132
133
 
133
134
  export type FilterFn<T, E extends Element> = (element: E) => T;
134
135
 
@@ -162,15 +163,15 @@ export type InteractorSpecification<E extends Element, F extends Filters<E>, A e
162
163
  }
163
164
 
164
165
  export type ActionMethods<E extends Element, A extends Actions<E, I>, I extends Interactor<E, any>> = {
165
- [P in keyof A]: A[P] extends ((interactor: I, ...args: infer TArgs) => Promise<infer TReturn>)
166
- ? ((...args: TArgs) => Interaction<TReturn>)
166
+ [P in keyof A]: A[P] extends ((interactor: I, ...args: infer TArgs) => Operation<infer TReturn>)
167
+ ? ((...args: TArgs) => ActionInteraction<E, TReturn>)
167
168
  : never;
168
169
  }
169
170
 
170
171
  export type FilterMethods<E extends Element, F extends Filters<E>> = {
171
172
  [P in keyof F]:
172
- F[P] extends FilterFn<infer TReturn, any> ? (() => Interaction<TReturn> & FilterObject<TReturn, Element>) :
173
- F[P] extends FilterObject<infer TReturn, any> ? (() => Interaction<TReturn> & FilterObject<TReturn, Element>) :
173
+ F[P] extends FilterFn<infer TReturn, any> ? (() => AssertionInteraction<E, TReturn> & FilterObject<TReturn, Element>) :
174
+ F[P] extends FilterObject<infer TReturn, any> ? (() => AssertionInteraction<E, TReturn> & FilterObject<TReturn, Element>) :
174
175
  never;
175
176
  }
176
177
 
@@ -198,6 +199,7 @@ export type FilterParams<E extends Element, F extends Filters<E>> = keyof F exte
198
199
  * @typeParam A the actions of this interactor, this is usually inferred from the specification
199
200
  */
200
201
  export interface InteractorConstructor<E extends Element, FP extends FilterParams<any, any>, FM extends FilterMethods<any, any>, AM extends ActionMethods<any, any, any>> {
202
+ interactorName: string;
201
203
  selector(value: string | SelectorFn<E>): InteractorConstructor<E, FP, FM, AM>;
202
204
  locator(value: FilterDefinition<string, E>): InteractorConstructor<E, FP, FM, AM>;
203
205
  filters<FR extends Filters<E>>(filters: FR): InteractorConstructor<E, MergeObjects<FP, FilterParams<E, FR>>, MergeObjects<FM, FilterMethods<E, FR>>, AM>;
@@ -225,6 +227,7 @@ export interface InteractorConstructor<E extends Element, FP extends FilterParam
225
227
  *
226
228
  * ``` typescript
227
229
  * Link('Home');
230
+ * Link(/^home/i);
228
231
  * ```
229
232
  *
230
233
  * Or with a locator and options:
@@ -236,7 +239,7 @@ export interface InteractorConstructor<E extends Element, FP extends FilterParam
236
239
  * @param value The locator value, which should match the value of applying the locator function defined in the {@link InteractorSpecification} to the element.
237
240
  * @param filters An object describing a set of filters to apply, which should match the value of applying the filters defined in the {@link InteractorSpecification} to the element.
238
241
  */
239
- (value: MaybeMatcher<string>, filters?: FP): Interactor<E, FP> & FM & AM;
242
+ (value: MaybeMatcher<string> | RegExp, filters?: FP): Interactor<E, FP> & FM & AM;
240
243
  }
241
244
 
242
245
  export type InteractorOptions<E extends Element, F extends Filters<E>, A extends Actions<E, Interactor<E, EmptyObject>>> = {
@@ -1,30 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatTable = void 0;
4
- const MAX_COLUMN_WIDTH = 40;
5
- function formatValue(value, width) {
6
- if (value.length > width) {
7
- return value.slice(0, width - 1) + '…';
8
- }
9
- else {
10
- return value.padEnd(width);
11
- }
12
- }
13
- function formatTable(options) {
14
- let columnWidths = options.headers.map((h, index) => {
15
- return Math.min(MAX_COLUMN_WIDTH, Math.max(h.length, ...options.rows.map((r) => r[index].length)));
16
- });
17
- let formatRow = (cells) => {
18
- return '┃ ' + cells.map((c, index) => formatValue(c, columnWidths[index])).join(' ┃ ') + ' ┃';
19
- };
20
- let spacerRow = () => {
21
- return '┣━' + columnWidths.map((w) => "━".repeat(w)).join('━╋━') + '━┫';
22
- };
23
- return [
24
- formatRow(options.headers),
25
- spacerRow(),
26
- ...options.rows.map((row) => formatRow(row))
27
- ].join('\n');
28
- }
29
- exports.formatTable = formatTable;
30
- //# sourceMappingURL=format-table.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"format-table.js","sourceRoot":"","sources":["../../src/format-table.ts"],"names":[],"mappings":";;;AAKA,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAE3B,SAAS,WAAW,CAAC,KAAa,EAAE,KAAa;IAC/C,IAAG,KAAK,CAAC,MAAM,GAAG,KAAK,EAAE;QACvB,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;KACxC;SAAM;QACL,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;KAC5B;AACH,CAAC;AAED,SAAgB,WAAW,CAAC,OAAqB;IAC/C,IAAI,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;QAClD,OAAO,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACrG,CAAC,CAAC,CAAC;IAEH,IAAI,SAAS,GAAG,CAAC,KAAe,EAAE,EAAE;QAClC,OAAO,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAChG,CAAC,CAAA;IAED,IAAI,SAAS,GAAG,GAAG,EAAE;QACnB,OAAO,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAC1E,CAAC,CAAA;IAED,OAAO;QACL,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC;QAC1B,SAAS,EAAE;QACX,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;KAC7C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAlBD,kCAkBC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"format-table.js","sourceRoot":"","sources":["../../src/format-table.ts"],"names":[],"mappings":"AAKA,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAE3B,SAAS,WAAW,CAAC,KAAa,EAAE,KAAa;IAC/C,IAAG,KAAK,CAAC,MAAM,GAAG,KAAK,EAAE;QACvB,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;KACxC;SAAM;QACL,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;KAC5B;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAqB;IAC/C,IAAI,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;QAClD,OAAO,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACrG,CAAC,CAAC,CAAC;IAEH,IAAI,SAAS,GAAG,CAAC,KAAe,EAAE,EAAE;QAClC,OAAO,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAChG,CAAC,CAAA;IAED,IAAI,SAAS,GAAG,GAAG,EAAE;QACnB,OAAO,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAC1E,CAAC,CAAA;IAED,OAAO;QACL,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC;QAC1B,SAAS,EAAE;QACX,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;KAC7C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -1,6 +0,0 @@
1
- export interface TableOptions {
2
- headers: string[];
3
- rows: string[][];
4
- }
5
- export declare function formatTable(options: TableOptions): string;
6
- //# sourceMappingURL=format-table.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"format-table.d.ts","sourceRoot":"","sources":["../src/format-table.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;CAClB;AAYD,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAkBzD"}
@@ -1,34 +0,0 @@
1
- export interface TableOptions {
2
- headers: string[];
3
- rows: string[][];
4
- }
5
-
6
- const MAX_COLUMN_WIDTH = 40
7
-
8
- function formatValue(value: string, width: number) {
9
- if(value.length > width) {
10
- return value.slice(0, width - 1) + '…';
11
- } else {
12
- return value.padEnd(width);
13
- }
14
- }
15
-
16
- export function formatTable(options: TableOptions): string {
17
- let columnWidths = options.headers.map((h, index) => {
18
- return Math.min(MAX_COLUMN_WIDTH, Math.max(h.length, ...options.rows.map((r) => r[index].length)));
19
- });
20
-
21
- let formatRow = (cells: string[]) => {
22
- return '┃ ' + cells.map((c, index) => formatValue(c, columnWidths[index])).join(' ┃ ') + ' ┃';
23
- }
24
-
25
- let spacerRow = () => {
26
- return '┣━' + columnWidths.map((w) => "━".repeat(w)).join('━╋━') + '━┫';
27
- }
28
-
29
- return [
30
- formatRow(options.headers),
31
- spacerRow(),
32
- ...options.rows.map((row) => formatRow(row))
33
- ].join('\n');
34
- }