@teleporthq/teleport-plugin-next-data-source 0.43.75 → 0.44.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.
- package/__tests__/_helpers/first-page-runtime.ts +184 -0
- package/__tests__/_helpers/pagination-widget-fixture.ts +154 -5
- package/__tests__/booking-item-transform.test.ts +120 -0
- package/__tests__/browser-row-policy.test.ts +210 -4
- package/__tests__/data-source-request-locale.test.ts +4 -3
- package/__tests__/fetchers.test.ts +7 -3
- package/__tests__/fixed-list-sort.test.ts +1 -1
- package/__tests__/infinite-scroll.test.ts +363 -9
- package/__tests__/list-sort-order.test.ts +61 -0
- package/__tests__/null-filter-where.test.ts +132 -0
- package/__tests__/page-data-visitor-read.test.ts +196 -0
- package/__tests__/pg-date-parser.test.ts +73 -0
- package/__tests__/plain-list-sort.test.ts +2 -2
- package/__tests__/plain-list-starting-sort.test.ts +323 -0
- package/__tests__/product-price-sort.ts +11 -6
- package/__tests__/range-control.test.ts +159 -0
- package/__tests__/refetch-loading-state.test.ts +158 -2
- package/__tests__/search-input-marker.test.ts +86 -0
- package/__tests__/search-url-sync.test.ts +34 -8
- package/__tests__/starting-sort-first-page.test.ts +285 -0
- package/__tests__/table-read-route-protected-tables.test.ts +33 -3
- package/dist/cjs/cache/ast.d.ts +2 -0
- package/dist/cjs/cache/ast.d.ts.map +1 -1
- package/dist/cjs/cache/ast.js +5 -2
- package/dist/cjs/cache/ast.js.map +1 -1
- package/dist/cjs/count-effect.d.ts +7 -1
- package/dist/cjs/count-effect.d.ts.map +1 -1
- package/dist/cjs/count-effect.js +15 -5
- package/dist/cjs/count-effect.js.map +1 -1
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js +10 -2
- package/dist/cjs/data-source-fetchers.js.map +1 -1
- package/dist/cjs/fetchers/pg-client-import.d.ts +12 -0
- package/dist/cjs/fetchers/pg-client-import.d.ts.map +1 -0
- package/dist/cjs/fetchers/pg-client-import.js +15 -0
- package/dist/cjs/fetchers/pg-client-import.js.map +1 -0
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +3 -1
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/raw-query.d.ts.map +1 -1
- package/dist/cjs/fetchers/raw-query.js +2 -1
- package/dist/cjs/fetchers/raw-query.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +4 -2
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.js +5 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +14 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/infinite-scroll.d.ts +89 -4
- package/dist/cjs/infinite-scroll.d.ts.map +1 -1
- package/dist/cjs/infinite-scroll.js +243 -33
- package/dist/cjs/infinite-scroll.js.map +1 -1
- package/dist/cjs/list-sort-order.d.ts +14 -0
- package/dist/cjs/list-sort-order.d.ts.map +1 -0
- package/dist/cjs/list-sort-order.js +36 -0
- package/dist/cjs/list-sort-order.js.map +1 -0
- package/dist/cjs/loading-state.d.ts +23 -2
- package/dist/cjs/loading-state.d.ts.map +1 -1
- package/dist/cjs/loading-state.js +50 -15
- package/dist/cjs/loading-state.js.map +1 -1
- package/dist/cjs/pagination-controls.d.ts +22 -3
- package/dist/cjs/pagination-controls.d.ts.map +1 -1
- package/dist/cjs/pagination-controls.js +29 -3
- package/dist/cjs/pagination-controls.js.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +336 -164
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/range-control.d.ts +45 -0
- package/dist/cjs/range-control.d.ts.map +1 -0
- package/dist/cjs/range-control.js +165 -0
- package/dist/cjs/range-control.js.map +1 -0
- package/dist/cjs/row-order.d.ts +3 -2
- package/dist/cjs/row-order.d.ts.map +1 -1
- package/dist/cjs/row-order.js +5 -4
- package/dist/cjs/row-order.js.map +1 -1
- package/dist/cjs/search-input.d.ts +42 -0
- package/dist/cjs/search-input.d.ts.map +1 -0
- package/dist/cjs/search-input.js +158 -0
- package/dist/cjs/search-input.js.map +1 -0
- package/dist/cjs/sort-utils.d.ts +53 -1
- package/dist/cjs/sort-utils.d.ts.map +1 -1
- package/dist/cjs/sort-utils.js +105 -13
- package/dist/cjs/sort-utils.js.map +1 -1
- package/dist/cjs/transformations/booking-item.d.ts +24 -0
- package/dist/cjs/transformations/booking-item.d.ts.map +1 -0
- package/dist/cjs/transformations/booking-item.js +27 -0
- package/dist/cjs/transformations/booking-item.js.map +1 -0
- package/dist/cjs/transformations/index.d.ts +4 -2
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +14 -5
- package/dist/cjs/transformations/index.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +3 -2
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +48 -18
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/cache/ast.d.ts +2 -0
- package/dist/esm/cache/ast.d.ts.map +1 -1
- package/dist/esm/cache/ast.js +3 -1
- package/dist/esm/cache/ast.js.map +1 -1
- package/dist/esm/count-effect.d.ts +7 -1
- package/dist/esm/count-effect.d.ts.map +1 -1
- package/dist/esm/count-effect.js +15 -5
- package/dist/esm/count-effect.js.map +1 -1
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js +10 -2
- package/dist/esm/data-source-fetchers.js.map +1 -1
- package/dist/esm/fetchers/pg-client-import.d.ts +12 -0
- package/dist/esm/fetchers/pg-client-import.d.ts.map +1 -0
- package/dist/esm/fetchers/pg-client-import.js +12 -0
- package/dist/esm/fetchers/pg-client-import.js.map +1 -0
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +3 -1
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/raw-query.d.ts.map +1 -1
- package/dist/esm/fetchers/raw-query.js +2 -1
- package/dist/esm/fetchers/raw-query.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +4 -2
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.js +6 -2
- package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +14 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/infinite-scroll.d.ts +89 -4
- package/dist/esm/infinite-scroll.d.ts.map +1 -1
- package/dist/esm/infinite-scroll.js +237 -32
- package/dist/esm/infinite-scroll.js.map +1 -1
- package/dist/esm/list-sort-order.d.ts +14 -0
- package/dist/esm/list-sort-order.d.ts.map +1 -0
- package/dist/esm/list-sort-order.js +32 -0
- package/dist/esm/list-sort-order.js.map +1 -0
- package/dist/esm/loading-state.d.ts +23 -2
- package/dist/esm/loading-state.d.ts.map +1 -1
- package/dist/esm/loading-state.js +50 -15
- package/dist/esm/loading-state.js.map +1 -1
- package/dist/esm/pagination-controls.d.ts +22 -3
- package/dist/esm/pagination-controls.d.ts.map +1 -1
- package/dist/esm/pagination-controls.js +28 -2
- package/dist/esm/pagination-controls.js.map +1 -1
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +340 -168
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/range-control.d.ts +45 -0
- package/dist/esm/range-control.d.ts.map +1 -0
- package/dist/esm/range-control.js +137 -0
- package/dist/esm/range-control.js.map +1 -0
- package/dist/esm/row-order.d.ts +3 -2
- package/dist/esm/row-order.d.ts.map +1 -1
- package/dist/esm/row-order.js +5 -4
- package/dist/esm/row-order.js.map +1 -1
- package/dist/esm/search-input.d.ts +42 -0
- package/dist/esm/search-input.d.ts.map +1 -0
- package/dist/esm/search-input.js +129 -0
- package/dist/esm/search-input.js.map +1 -0
- package/dist/esm/sort-utils.d.ts +53 -1
- package/dist/esm/sort-utils.d.ts.map +1 -1
- package/dist/esm/sort-utils.js +100 -12
- package/dist/esm/sort-utils.js.map +1 -1
- package/dist/esm/transformations/booking-item.d.ts +24 -0
- package/dist/esm/transformations/booking-item.d.ts.map +1 -0
- package/dist/esm/transformations/booking-item.js +23 -0
- package/dist/esm/transformations/booking-item.js.map +1 -0
- package/dist/esm/transformations/index.d.ts +4 -2
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +14 -5
- package/dist/esm/transformations/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +3 -2
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +49 -19
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/cache/ast.ts +4 -1
- package/src/count-effect.ts +31 -8
- package/src/data-source-fetchers.ts +33 -14
- package/src/fetchers/pg-client-import.ts +16 -0
- package/src/fetchers/postgresql.ts +15 -9
- package/src/fetchers/raw-query.ts +2 -1
- package/src/fetchers/teleport.ts +19 -10
- package/src/fetchers/utils/browser-row-policy.ts +79 -5
- package/src/index.ts +8 -2
- package/src/infinite-scroll.ts +420 -81
- package/src/list-sort-order.ts +34 -0
- package/src/loading-state.ts +85 -13
- package/src/pagination-controls.ts +26 -3
- package/src/pagination-plugin.ts +532 -218
- package/src/range-control.ts +207 -0
- package/src/row-order.ts +5 -4
- package/src/search-input.ts +190 -0
- package/src/sort-utils.ts +164 -21
- package/src/transformations/booking-item.ts +71 -0
- package/src/transformations/index.ts +25 -2
- package/src/utils.ts +55 -18
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/* tslint:disable:function-constructor max-classes-per-file no-any */
|
|
2
|
+
import * as types from '@babel/types'
|
|
3
|
+
import generator from '@babel/generator'
|
|
4
|
+
import { generateDataSourceFetcherWithCore } from '../../src/data-source-fetchers'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Runs what a generated page asks its data module for: the arguments its
|
|
8
|
+
* `getStaticProps` hands `fetchData`, the params a provider computes in the
|
|
9
|
+
* browser, and the products route both reach — so a test can check that the
|
|
10
|
+
* build and the browser order one row set the same way.
|
|
11
|
+
*
|
|
12
|
+
* `__tests__/_helpers` is excluded from the jest test match, so this file is a
|
|
13
|
+
* fixture rather than a suite.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const isFetchDataCall = (node: types.Node): node is types.CallExpression =>
|
|
17
|
+
node.type === 'CallExpression' &&
|
|
18
|
+
node.callee.type === 'MemberExpression' &&
|
|
19
|
+
node.callee.property.type === 'Identifier' &&
|
|
20
|
+
node.callee.property.name === 'fetchData' &&
|
|
21
|
+
node.arguments[0]?.type === 'ObjectExpression'
|
|
22
|
+
|
|
23
|
+
// The `Promise.all` entry whose result lands in `propKey`.
|
|
24
|
+
const prefetchOf = (content: types.Node, propKey: string): types.Node | undefined => {
|
|
25
|
+
let entry: types.Node | undefined
|
|
26
|
+
types.traverseFast(content, (node) => {
|
|
27
|
+
if (
|
|
28
|
+
!entry &&
|
|
29
|
+
node.type === 'VariableDeclarator' &&
|
|
30
|
+
node.id.type === 'ArrayPattern' &&
|
|
31
|
+
node.init?.type === 'AwaitExpression' &&
|
|
32
|
+
node.init.argument.type === 'CallExpression' &&
|
|
33
|
+
node.init.argument.arguments[0]?.type === 'ArrayExpression'
|
|
34
|
+
) {
|
|
35
|
+
const index = node.id.elements.findIndex(
|
|
36
|
+
(element) => element?.type === 'Identifier' && element.name === propKey
|
|
37
|
+
)
|
|
38
|
+
if (index !== -1) {
|
|
39
|
+
entry = (node.init.argument.arguments[0] as types.ArrayExpression).elements[index] as
|
|
40
|
+
| types.Node
|
|
41
|
+
| undefined
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
return entry
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* What getStaticProps hands fetchData — `{ page: 1, perPage: 20, …, sorts }`,
|
|
50
|
+
* then who it reads for — for the prefetch filling `propKey`, or the first one.
|
|
51
|
+
*/
|
|
52
|
+
export const buildTimeArguments = (
|
|
53
|
+
staticPropsChunk: any,
|
|
54
|
+
propKey?: string
|
|
55
|
+
): Array<Record<string, unknown>> => {
|
|
56
|
+
const scope = propKey ? prefetchOf(staticPropsChunk.content, propKey) : staticPropsChunk.content
|
|
57
|
+
let call: types.CallExpression | undefined
|
|
58
|
+
types.traverseFast(scope as types.Node, (node) => {
|
|
59
|
+
if (!call && isFetchDataCall(node)) {
|
|
60
|
+
call = node
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
const args = call!.arguments.map((arg) => generator(arg as types.Node).code)
|
|
64
|
+
return new Function(`return [${args.join(', ')}]`)()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The `nth` memoized `params` a provider of the component sends.
|
|
68
|
+
const browserParamsBody = (chunk: any, nth: number): types.Expression => {
|
|
69
|
+
const bodies: types.Expression[] = []
|
|
70
|
+
types.traverseFast(chunk.content, (node) => {
|
|
71
|
+
if (
|
|
72
|
+
node.type === 'JSXAttribute' &&
|
|
73
|
+
node.name.name === 'params' &&
|
|
74
|
+
node.value?.type === 'JSXExpressionContainer' &&
|
|
75
|
+
node.value.expression.type === 'CallExpression'
|
|
76
|
+
) {
|
|
77
|
+
const arrow = node.value.expression.arguments[0]
|
|
78
|
+
if (arrow?.type === 'ArrowFunctionExpression') {
|
|
79
|
+
bodies.push(arrow.body as types.Expression)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
return bodies[nth]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The provider's params as the browser computes them for the given state, as they go on the wire. */
|
|
87
|
+
export const browserParams = (
|
|
88
|
+
chunk: any,
|
|
89
|
+
state: Record<string, unknown>,
|
|
90
|
+
nth: number = 0
|
|
91
|
+
): Record<string, string> => {
|
|
92
|
+
const names = Object.keys(state)
|
|
93
|
+
const params = new Function(
|
|
94
|
+
...names,
|
|
95
|
+
`return (${generator(browserParamsBody(chunk, nth)).code})`
|
|
96
|
+
)(...names.map((name) => state[name]))
|
|
97
|
+
// What `new URLSearchParams(params)` puts on the wire.
|
|
98
|
+
return Object.fromEntries(new URLSearchParams(params))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The products route as the project ships it, over a database that records
|
|
103
|
+
* the statements it is sent.
|
|
104
|
+
*/
|
|
105
|
+
export const bootProductsRoute = () => {
|
|
106
|
+
const dataSource = {
|
|
107
|
+
id: 'ds-1',
|
|
108
|
+
name: 'db',
|
|
109
|
+
type: 'teleport',
|
|
110
|
+
config: {
|
|
111
|
+
host: 'teleporthq.secrets.TELEPORT_DB_HOST',
|
|
112
|
+
port: 5432,
|
|
113
|
+
user: 'teleporthq.secrets.TELEPORT_DB_USER',
|
|
114
|
+
password: 'teleporthq.secrets.TELEPORT_DB_PASSWORD',
|
|
115
|
+
database: 'teleporthq.secrets.TELEPORT_DB_NAME',
|
|
116
|
+
selectedTables: { teleport_products: { columns: [{ name: 'id', type: 'uuid' }] } },
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
const code = generateDataSourceFetcherWithCore(dataSource as never, 'teleport_products')
|
|
120
|
+
.replace(
|
|
121
|
+
"import { Client, types as pgTypes } from 'pg'",
|
|
122
|
+
"const { Client, types: pgTypes } = require('pg')"
|
|
123
|
+
)
|
|
124
|
+
.replace(/^export \{[^}]*\}\s*$/m, '')
|
|
125
|
+
.replace(/^export default \{[^}]*\}\s*$/m, '')
|
|
126
|
+
.concat('\nmodule.exports = { handler: handler, fetchData: fetchData }')
|
|
127
|
+
expect(code).not.toMatch(/^(export|import) /m)
|
|
128
|
+
|
|
129
|
+
const statements: string[] = []
|
|
130
|
+
class FakeClient {
|
|
131
|
+
public async connect(): Promise<void> {
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
public async end(): Promise<void> {
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
public async query(sql: string): Promise<any> {
|
|
138
|
+
statements.push(sql)
|
|
139
|
+
return { rows: [], rowCount: 0 }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const fakeRequire = (name: string): any => {
|
|
143
|
+
if (name === 'pg') {
|
|
144
|
+
return { Client: FakeClient }
|
|
145
|
+
}
|
|
146
|
+
if (name === 'next-auth/jwt') {
|
|
147
|
+
return { getToken: async (): Promise<null> => null }
|
|
148
|
+
}
|
|
149
|
+
throw new Error('unexpected require: ' + name)
|
|
150
|
+
}
|
|
151
|
+
const moduleObj: { exports: any } = { exports: {} }
|
|
152
|
+
new Function('require', 'module', 'exports', 'process', 'console', code)(
|
|
153
|
+
fakeRequire,
|
|
154
|
+
moduleObj,
|
|
155
|
+
moduleObj.exports,
|
|
156
|
+
{
|
|
157
|
+
env: {
|
|
158
|
+
NEXTAUTH_SECRET: 'secret',
|
|
159
|
+
TELEPORT_DB_HOST: 'h',
|
|
160
|
+
TELEPORT_DB_USER: 'u',
|
|
161
|
+
TELEPORT_DB_PASSWORD: 'p',
|
|
162
|
+
TELEPORT_DB_NAME: 'd',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{ error: (): void => undefined, warn: (): void => undefined, log: (): void => undefined }
|
|
166
|
+
)
|
|
167
|
+
const lastSelect = (): string =>
|
|
168
|
+
statements.filter((sql) => /^\s*SELECT \* FROM/i.test(sql)).pop() || ''
|
|
169
|
+
return {
|
|
170
|
+
atBuild: async (args: Array<Record<string, unknown>>) => {
|
|
171
|
+
await moduleObj.exports.fetchData(...args)
|
|
172
|
+
return lastSelect()
|
|
173
|
+
},
|
|
174
|
+
inBrowser: async (query: Record<string, string>) => {
|
|
175
|
+
const res = { status: () => res, json: () => res, setHeader: () => res }
|
|
176
|
+
await moduleObj.exports.handler({ method: 'GET', query, headers: {} }, res)
|
|
177
|
+
return lastSelect()
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const orderBy = (sql: string): string => (sql.match(/ ORDER BY .*? LIMIT/) || [''])[0]
|
|
183
|
+
export const rowWindow = (sql: string): string =>
|
|
184
|
+
(sql.match(/ LIMIT \d+( OFFSET \d+)?$/) || [''])[0]
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import * as types from '@babel/types'
|
|
2
2
|
import { ChunkType, FileType, ChunkDefinition } from '@teleporthq/teleport-types'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
PAGINATION_CONTROL_ATTR,
|
|
5
|
+
PAGINATION_FOR_ATTR,
|
|
6
|
+
RANGE_TEMPLATE_ATTR,
|
|
7
|
+
} from '../../src/pagination-controls'
|
|
8
|
+
import { SEARCH_FOR_ATTR } from '../../src/search-input'
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* A component shell carrying a `DataProvider` and the pagination controls a
|
|
@@ -20,9 +25,36 @@ interface ControlOptions {
|
|
|
20
25
|
legacyOnly?: boolean
|
|
21
26
|
/** Number of DataProvider + widget pairs to emit (order-based zip coverage). */
|
|
22
27
|
mappers?: number
|
|
28
|
+
/** The element the providers render in: a table's body, a list. */
|
|
29
|
+
rootTag?: string
|
|
30
|
+
/**
|
|
31
|
+
* The `data-tq-range-template` of a `range` control in the widget. Defaults
|
|
32
|
+
* to the "Showing" wording; `null` authors the control with no template.
|
|
33
|
+
*/
|
|
34
|
+
rangeTemplate?: string | null
|
|
35
|
+
/**
|
|
36
|
+
* A `range` text OUTSIDE every widget, placed BEFORE the first provider (a
|
|
37
|
+
* toolbar above the list), naming its mapper with `data-tq-pagination-for`.
|
|
38
|
+
*/
|
|
39
|
+
standaloneRange?: { forNodeId: string; template?: string }
|
|
40
|
+
/**
|
|
41
|
+
* Search inputs OUTSIDE every widget, placed BEFORE the first provider, in
|
|
42
|
+
* this order: a page's own box naming its mapper with `data-tq-search-for`,
|
|
43
|
+
* or the platform's (`search-input` class) paired by order.
|
|
44
|
+
*/
|
|
45
|
+
standaloneSearch?: Array<{ forNodeId?: string; name: string }>
|
|
23
46
|
}
|
|
24
47
|
|
|
25
|
-
|
|
48
|
+
// ASCII hyphen on purpose: the generator prints string literals through jsesc,
|
|
49
|
+
// which would turn an en dash into `–` and make every assertion unreadable.
|
|
50
|
+
export const RANGE_DRAWN_TEXT = 'Showing 1-8 of 12'
|
|
51
|
+
export const RANGE_TEMPLATE = 'Showing {from}–{to} of {total}'
|
|
52
|
+
|
|
53
|
+
const control = (
|
|
54
|
+
marker: string,
|
|
55
|
+
children: types.JSXElement['children'] = [],
|
|
56
|
+
extraAttrs: Array<[string, string]> = []
|
|
57
|
+
): types.JSXElement =>
|
|
26
58
|
types.jsxElement(
|
|
27
59
|
types.jsxOpeningElement(
|
|
28
60
|
types.jsxIdentifier('div'),
|
|
@@ -35,6 +67,9 @@ const control = (marker: string, children: types.JSXElement['children'] = []): t
|
|
|
35
67
|
types.jsxIdentifier(PAGINATION_CONTROL_ATTR),
|
|
36
68
|
types.stringLiteral(marker)
|
|
37
69
|
),
|
|
70
|
+
...extraAttrs.map(([name, value]) =>
|
|
71
|
+
types.jsxAttribute(types.jsxIdentifier(name), types.stringLiteral(value))
|
|
72
|
+
),
|
|
38
73
|
],
|
|
39
74
|
false
|
|
40
75
|
),
|
|
@@ -43,6 +78,40 @@ const control = (marker: string, children: types.JSXElement['children'] = []): t
|
|
|
43
78
|
false
|
|
44
79
|
)
|
|
45
80
|
|
|
81
|
+
const rangeControl = (
|
|
82
|
+
template: string | null | undefined,
|
|
83
|
+
forNodeId?: string
|
|
84
|
+
): types.JSXElement => {
|
|
85
|
+
const attrs: Array<[string, string]> = []
|
|
86
|
+
if (template !== null) {
|
|
87
|
+
attrs.push([RANGE_TEMPLATE_ATTR, template ?? RANGE_TEMPLATE])
|
|
88
|
+
}
|
|
89
|
+
if (forNodeId) {
|
|
90
|
+
attrs.push([PAGINATION_FOR_ATTR, forNodeId])
|
|
91
|
+
}
|
|
92
|
+
return control('range', [types.jsxText(RANGE_DRAWN_TEXT)], attrs)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const searchInput = (name: string, forNodeId?: string): types.JSXElement =>
|
|
96
|
+
types.jsxElement(
|
|
97
|
+
types.jsxOpeningElement(
|
|
98
|
+
types.jsxIdentifier('input'),
|
|
99
|
+
[
|
|
100
|
+
types.jsxAttribute(types.jsxIdentifier('name'), types.stringLiteral(name)),
|
|
101
|
+
forNodeId
|
|
102
|
+
? types.jsxAttribute(types.jsxIdentifier(SEARCH_FOR_ATTR), types.stringLiteral(forNodeId))
|
|
103
|
+
: types.jsxAttribute(
|
|
104
|
+
types.jsxIdentifier('className'),
|
|
105
|
+
types.stringLiteral('products-list-search-input')
|
|
106
|
+
),
|
|
107
|
+
],
|
|
108
|
+
true
|
|
109
|
+
),
|
|
110
|
+
null,
|
|
111
|
+
[],
|
|
112
|
+
true
|
|
113
|
+
)
|
|
114
|
+
|
|
46
115
|
const legacyControl = (direction: 'previous' | 'next'): types.JSXElement =>
|
|
47
116
|
types.jsxElement(
|
|
48
117
|
types.jsxOpeningElement(
|
|
@@ -72,6 +141,10 @@ const buildWidget = (options: ControlOptions): types.JSXElement => {
|
|
|
72
141
|
children.push(legacyControl(marker))
|
|
73
142
|
continue
|
|
74
143
|
}
|
|
144
|
+
if (marker === 'range') {
|
|
145
|
+
children.push(rangeControl(options.rangeTemplate))
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
75
148
|
children.push(control(marker))
|
|
76
149
|
}
|
|
77
150
|
|
|
@@ -148,6 +221,12 @@ const buildDataProvider = (name: string, rowIdentifier: string): types.JSXElemen
|
|
|
148
221
|
|
|
149
222
|
export const makeWidgetComponentChunk = (options: ControlOptions): ChunkDefinition => {
|
|
150
223
|
const sections: types.JSXElement[] = []
|
|
224
|
+
if (options.standaloneRange) {
|
|
225
|
+
sections.push(rangeControl(options.standaloneRange.template, options.standaloneRange.forNodeId))
|
|
226
|
+
}
|
|
227
|
+
for (const search of options.standaloneSearch ?? []) {
|
|
228
|
+
sections.push(searchInput(search.name, search.forNodeId))
|
|
229
|
+
}
|
|
151
230
|
const mappers = options.mappers ?? 1
|
|
152
231
|
for (let i = 0; i < mappers; i += 1) {
|
|
153
232
|
const identifier = `items${i === 0 ? '' : i}`
|
|
@@ -155,9 +234,10 @@ export const makeWidgetComponentChunk = (options: ControlOptions): ChunkDefiniti
|
|
|
155
234
|
sections.push(buildWidget(options))
|
|
156
235
|
}
|
|
157
236
|
|
|
237
|
+
const rootTag = options.rootTag ?? 'div'
|
|
158
238
|
const root = types.jsxElement(
|
|
159
|
-
types.jsxOpeningElement(types.jsxIdentifier(
|
|
160
|
-
types.jsxClosingElement(types.jsxIdentifier(
|
|
239
|
+
types.jsxOpeningElement(types.jsxIdentifier(rootTag), [], false),
|
|
240
|
+
types.jsxClosingElement(types.jsxIdentifier(rootTag)),
|
|
161
241
|
sections,
|
|
162
242
|
false
|
|
163
243
|
)
|
|
@@ -189,6 +269,16 @@ export interface RepeaterOptions {
|
|
|
189
269
|
perPage?: number
|
|
190
270
|
/** The list's filters, as the inspector authors them (conditions and groups). */
|
|
191
271
|
filters?: unknown[]
|
|
272
|
+
/** The mapper's editor node id, as the UIDL carries it on the repeater. */
|
|
273
|
+
nodeId?: string
|
|
274
|
+
/** The repeater's sort and direction as the UIDL carries them: a static column, or an expression. */
|
|
275
|
+
sort?: unknown
|
|
276
|
+
sortDirection?: unknown
|
|
277
|
+
/** The repeater's cache config. */
|
|
278
|
+
cache?: unknown
|
|
279
|
+
/** The repeater's `source`, for a list bound inside the payload (with `dataSourceType`). */
|
|
280
|
+
source?: string
|
|
281
|
+
dataSourceType?: string
|
|
192
282
|
}
|
|
193
283
|
|
|
194
284
|
export const makeUidlNode = (options: RepeaterOptions, identifier: string = 'items'): any => ({
|
|
@@ -198,7 +288,7 @@ export const makeUidlNode = (options: RepeaterOptions, identifier: string = 'ite
|
|
|
198
288
|
resourceDefinition: {
|
|
199
289
|
dataSourceId: 'ds1',
|
|
200
290
|
tableName: 'products',
|
|
201
|
-
dataSourceType: 'postgresql',
|
|
291
|
+
dataSourceType: options.dataSourceType ?? 'postgresql',
|
|
202
292
|
},
|
|
203
293
|
resource: {
|
|
204
294
|
params: {
|
|
@@ -218,6 +308,11 @@ export const makeUidlNode = (options: RepeaterOptions, identifier: string = 'ite
|
|
|
218
308
|
...(options.paginationMode ? { paginationMode: options.paginationMode } : {}),
|
|
219
309
|
...(options.infiniteScroll ? { infiniteScroll: true } : {}),
|
|
220
310
|
...(options.infiniteScrollLoadMore ? { infiniteScrollLoadMore: true } : {}),
|
|
311
|
+
...(options.nodeId ? { nodeId: options.nodeId } : {}),
|
|
312
|
+
...(options.sort ? { sort: options.sort } : {}),
|
|
313
|
+
...(options.sortDirection ? { sortDirection: options.sortDirection } : {}),
|
|
314
|
+
...(options.cache ? { cache: options.cache } : {}),
|
|
315
|
+
...(options.source ? { source: options.source } : {}),
|
|
221
316
|
nodes: { list: { type: 'element', content: { elementType: 'div' } } },
|
|
222
317
|
},
|
|
223
318
|
},
|
|
@@ -235,3 +330,57 @@ export const makeMultiMapperUidl = (repeaters: RepeaterOptions[]): any => ({
|
|
|
235
330
|
),
|
|
236
331
|
},
|
|
237
332
|
})
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The page's `getStaticProps` as the main data-source plugin leaves it: a
|
|
336
|
+
* `Promise.all` of prefetches and a `props` object, which the pagination plugin
|
|
337
|
+
* extends with each paginated list's first page and its count. Its presence is
|
|
338
|
+
* what makes the component a page.
|
|
339
|
+
*/
|
|
340
|
+
export const makeGetStaticPropsChunk = (): ChunkDefinition => ({
|
|
341
|
+
name: 'getStaticProps',
|
|
342
|
+
type: ChunkType.AST,
|
|
343
|
+
fileType: FileType.JS,
|
|
344
|
+
linkAfter: ['jsx-component'],
|
|
345
|
+
meta: {},
|
|
346
|
+
content: types.exportNamedDeclaration(
|
|
347
|
+
types.functionDeclaration(
|
|
348
|
+
types.identifier('getStaticProps'),
|
|
349
|
+
[types.identifier('context')],
|
|
350
|
+
types.blockStatement([
|
|
351
|
+
types.tryStatement(
|
|
352
|
+
types.blockStatement([
|
|
353
|
+
types.variableDeclaration('const', [
|
|
354
|
+
types.variableDeclarator(
|
|
355
|
+
types.arrayPattern([]),
|
|
356
|
+
types.awaitExpression(
|
|
357
|
+
types.callExpression(
|
|
358
|
+
types.memberExpression(types.identifier('Promise'), types.identifier('all')),
|
|
359
|
+
[types.arrayExpression([])]
|
|
360
|
+
)
|
|
361
|
+
)
|
|
362
|
+
),
|
|
363
|
+
]),
|
|
364
|
+
types.returnStatement(
|
|
365
|
+
types.objectExpression([
|
|
366
|
+
types.objectProperty(types.identifier('props'), types.objectExpression([])),
|
|
367
|
+
])
|
|
368
|
+
),
|
|
369
|
+
]),
|
|
370
|
+
types.catchClause(
|
|
371
|
+
types.identifier('error'),
|
|
372
|
+
types.blockStatement([
|
|
373
|
+
types.returnStatement(
|
|
374
|
+
types.objectExpression([
|
|
375
|
+
types.objectProperty(types.identifier('props'), types.objectExpression([])),
|
|
376
|
+
])
|
|
377
|
+
),
|
|
378
|
+
])
|
|
379
|
+
)
|
|
380
|
+
),
|
|
381
|
+
]),
|
|
382
|
+
false,
|
|
383
|
+
true
|
|
384
|
+
)
|
|
385
|
+
),
|
|
386
|
+
})
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { generateBookingItemTransformationCode } from '../src/transformations/booking-item'
|
|
2
|
+
import { generateSharedTransformationCode } from '../src/transformations/shared-utils'
|
|
3
|
+
import {
|
|
4
|
+
detectTransformationType,
|
|
5
|
+
getTransformExpression,
|
|
6
|
+
getTransformWrapperCode,
|
|
7
|
+
} from '../src/transformations'
|
|
8
|
+
|
|
9
|
+
// A booking page binds an item's columns as stored; the transform only puts
|
|
10
|
+
// its texts in the page's language. These evaluate the EMITTED code, the way
|
|
11
|
+
// the runtime does.
|
|
12
|
+
|
|
13
|
+
type Build = (record: unknown, options?: Record<string, unknown>) => Record<string, unknown>
|
|
14
|
+
|
|
15
|
+
const builderFor = (code: string): Build =>
|
|
16
|
+
new Function(
|
|
17
|
+
generateSharedTransformationCode() + '\n' + code + '\nreturn buildBookingItem;'
|
|
18
|
+
)() as Build
|
|
19
|
+
|
|
20
|
+
const buildBookingItem = builderFor(generateBookingItemTransformationCode())
|
|
21
|
+
|
|
22
|
+
const buildWithCategories = builderFor(
|
|
23
|
+
generateBookingItemTransformationCode({
|
|
24
|
+
categories: [
|
|
25
|
+
{
|
|
26
|
+
id: 'rooms',
|
|
27
|
+
name: 'Rooms',
|
|
28
|
+
slug: 'rooms',
|
|
29
|
+
parentId: null,
|
|
30
|
+
order: 0,
|
|
31
|
+
translations: { es: { name: 'Habitaciones' } },
|
|
32
|
+
children: [
|
|
33
|
+
{
|
|
34
|
+
id: 'suites',
|
|
35
|
+
name: 'Suites',
|
|
36
|
+
slug: 'suites',
|
|
37
|
+
parentId: 'rooms',
|
|
38
|
+
order: 0,
|
|
39
|
+
children: [],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
})
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const item = {
|
|
48
|
+
id: 'i1',
|
|
49
|
+
slug: 'double-room',
|
|
50
|
+
name: 'Double room',
|
|
51
|
+
es_name: 'Habitación doble',
|
|
52
|
+
description: '<p>Sea view.</p>',
|
|
53
|
+
es_description: '<p>Vista al mar.</p>',
|
|
54
|
+
location: 'Main street',
|
|
55
|
+
es_location: '',
|
|
56
|
+
price: '120.00',
|
|
57
|
+
booking_unit: 'night',
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe('the booking item transform', () => {
|
|
61
|
+
it('is applied to the booking items table only', () => {
|
|
62
|
+
expect(detectTransformationType('teleport_booking_items')).toBe('booking-item')
|
|
63
|
+
expect(detectTransformationType('public.TELEPORT_BOOKING_ITEMS')).toBe('booking-item')
|
|
64
|
+
expect(detectTransformationType('booking_items')).toBeNull()
|
|
65
|
+
expect(getTransformExpression('teleport_booking_items')).toContain('transformRecords')
|
|
66
|
+
expect(getTransformWrapperCode('teleport_booking_items')).toContain('transformBookingItems')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('shows the texts in the page’s language, every column kept as stored', () => {
|
|
70
|
+
const row = buildBookingItem(item, { currentLanguage: 'es', mainLanguage: 'en' })
|
|
71
|
+
expect(row).toMatchObject({
|
|
72
|
+
name: 'Habitación doble',
|
|
73
|
+
description: '<p>Vista al mar.</p>',
|
|
74
|
+
// Not translated yet: the main language's.
|
|
75
|
+
location: 'Main street',
|
|
76
|
+
slug: 'double-room',
|
|
77
|
+
price: '120.00',
|
|
78
|
+
booking_unit: 'night',
|
|
79
|
+
es_name: 'Habitación doble',
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('names its category the way the taxonomy names its first one in the page’s language', () => {
|
|
84
|
+
const filed = { ...item, category: 'Rooms', category_ids: '["rooms","suites"]' }
|
|
85
|
+
expect(buildWithCategories(filed, { currentLanguage: 'es', mainLanguage: 'en' }).category).toBe(
|
|
86
|
+
'Habitaciones'
|
|
87
|
+
)
|
|
88
|
+
expect(buildWithCategories(filed, { currentLanguage: 'en', mainLanguage: 'en' }).category).toBe(
|
|
89
|
+
'Rooms'
|
|
90
|
+
)
|
|
91
|
+
// No translation: the main name; a nested category resolves too.
|
|
92
|
+
expect(
|
|
93
|
+
buildWithCategories(
|
|
94
|
+
{ ...filed, category: 'Suites', category_ids: '["suites"]' },
|
|
95
|
+
{ currentLanguage: 'es', mainLanguage: 'en' }
|
|
96
|
+
).category
|
|
97
|
+
).toBe('Suites')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('keeps the stored category while none of its ids is a category', () => {
|
|
101
|
+
const legacy = { ...item, category: 'Rooms', category_ids: '["gone"]' }
|
|
102
|
+
expect(
|
|
103
|
+
buildWithCategories(legacy, { currentLanguage: 'es', mainLanguage: 'en' }).category
|
|
104
|
+
).toBe('Rooms')
|
|
105
|
+
expect(buildBookingItem(legacy, { currentLanguage: 'es', mainLanguage: 'en' }).category).toBe(
|
|
106
|
+
'Rooms'
|
|
107
|
+
)
|
|
108
|
+
// A row without category columns gains none.
|
|
109
|
+
expect(
|
|
110
|
+
'category' in buildWithCategories(item, { currentLanguage: 'es', mainLanguage: 'en' })
|
|
111
|
+
).toBe(false)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('keeps the main language on its own pages and without a localization', () => {
|
|
115
|
+
expect(buildBookingItem(item, { currentLanguage: 'en', mainLanguage: 'en' }).name).toBe(
|
|
116
|
+
'Double room'
|
|
117
|
+
)
|
|
118
|
+
expect(buildBookingItem(item, {}).name).toBe('Double room')
|
|
119
|
+
})
|
|
120
|
+
})
|