@teleporthq/teleport-plugin-next-data-source 0.43.74 → 0.44.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.
- package/__tests__/_helpers/pagination-widget-fixture.ts +95 -5
- package/__tests__/blog-adjacent-arranged.test.ts +176 -0
- package/__tests__/blog-post-contents.test.ts +212 -0
- package/__tests__/blog-post-page-enrichment.test.ts +28 -5
- package/__tests__/blog-post-picture-stand-in.test.ts +48 -0
- package/__tests__/blog-post-reading-time.test.ts +55 -0
- package/__tests__/booking-item-transform.test.ts +120 -0
- package/__tests__/browser-row-policy.test.ts +13 -3
- package/__tests__/content-preset-wiring.test.ts +108 -0
- package/__tests__/fetchers.test.ts +1 -1
- package/__tests__/fixed-list-sort.test.ts +183 -0
- package/__tests__/infinite-scroll.test.ts +154 -5
- package/__tests__/list-sort-order.test.ts +61 -0
- package/__tests__/null-filter-where.test.ts +132 -0
- package/__tests__/pagination-filter-groups.test.ts +186 -0
- package/__tests__/pg-date-parser.test.ts +73 -0
- package/__tests__/plain-list-sort.test.ts +215 -0
- package/__tests__/product-price-sort.ts +58 -25
- package/__tests__/range-control.test.ts +159 -0
- package/__tests__/rest-api-key-header.test.ts +18 -0
- package/__tests__/search-input-marker.test.ts +86 -0
- package/__tests__/search-url-sync.test.ts +61 -16
- package/__tests__/shared-resource-prefetch.test.ts +65 -0
- package/__tests__/table-read-route-protected-tables.test.ts +33 -3
- 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/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 +4 -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/rest-api.d.ts.map +1 -1
- package/dist/cjs/fetchers/rest-api.js +3 -1
- package/dist/cjs/fetchers/rest-api.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +5 -2
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts +7 -6
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.js +7 -33
- package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -1
- package/dist/cjs/filter-utils.d.ts +19 -7
- package/dist/cjs/filter-utils.d.ts.map +1 -1
- package/dist/cjs/filter-utils.js +49 -13
- package/dist/cjs/filter-utils.js.map +1 -1
- package/dist/cjs/infinite-scroll.d.ts +25 -2
- package/dist/cjs/infinite-scroll.d.ts.map +1 -1
- package/dist/cjs/infinite-scroll.js +144 -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/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 +267 -212
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/product-price-sort.d.ts +0 -9
- package/dist/cjs/product-price-sort.d.ts.map +1 -1
- package/dist/cjs/product-price-sort.js +1 -13
- package/dist/cjs/product-price-sort.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 +20 -0
- package/dist/cjs/row-order.d.ts.map +1 -0
- package/dist/cjs/row-order.js +46 -0
- package/dist/cjs/row-order.js.map +1 -0
- 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 +10 -0
- package/dist/cjs/sort-utils.d.ts.map +1 -1
- package/dist/cjs/sort-utils.js +22 -1
- package/dist/cjs/sort-utils.js.map +1 -1
- package/dist/cjs/transformations/blog-adjacent-posts.d.ts +11 -1
- package/dist/cjs/transformations/blog-adjacent-posts.d.ts.map +1 -1
- package/dist/cjs/transformations/blog-adjacent-posts.js +14 -1
- package/dist/cjs/transformations/blog-adjacent-posts.js.map +1 -1
- package/dist/cjs/transformations/blog-comments.d.ts +1 -1
- package/dist/cjs/transformations/blog-comments.d.ts.map +1 -1
- package/dist/cjs/transformations/blog-comments.js +1 -1
- package/dist/cjs/transformations/blog-comments.js.map +1 -1
- package/dist/cjs/transformations/blog-post.d.ts +14 -0
- package/dist/cjs/transformations/blog-post.d.ts.map +1 -1
- package/dist/cjs/transformations/blog-post.js +4 -1
- package/dist/cjs/transformations/blog-post.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/heading-anchors.d.ts +32 -8
- package/dist/cjs/transformations/heading-anchors.d.ts.map +1 -1
- package/dist/cjs/transformations/heading-anchors.js +34 -10
- package/dist/cjs/transformations/heading-anchors.js.map +1 -1
- package/dist/cjs/transformations/index.d.ts +27 -13
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +73 -16
- package/dist/cjs/transformations/index.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +52 -3
- package/dist/cjs/utils.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/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 +5 -2
- 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/rest-api.d.ts.map +1 -1
- package/dist/esm/fetchers/rest-api.js +3 -1
- package/dist/esm/fetchers/rest-api.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +6 -3
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts +7 -6
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.js +5 -31
- package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -1
- package/dist/esm/filter-utils.d.ts +19 -7
- package/dist/esm/filter-utils.d.ts.map +1 -1
- package/dist/esm/filter-utils.js +46 -12
- package/dist/esm/filter-utils.js.map +1 -1
- package/dist/esm/infinite-scroll.d.ts +25 -2
- package/dist/esm/infinite-scroll.d.ts.map +1 -1
- package/dist/esm/infinite-scroll.js +142 -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/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 +271 -216
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/product-price-sort.d.ts +0 -9
- package/dist/esm/product-price-sort.d.ts.map +1 -1
- package/dist/esm/product-price-sort.js +0 -11
- package/dist/esm/product-price-sort.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 +20 -0
- package/dist/esm/row-order.d.ts.map +1 -0
- package/dist/esm/row-order.js +41 -0
- package/dist/esm/row-order.js.map +1 -0
- 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 +10 -0
- package/dist/esm/sort-utils.d.ts.map +1 -1
- package/dist/esm/sort-utils.js +20 -0
- package/dist/esm/sort-utils.js.map +1 -1
- package/dist/esm/transformations/blog-adjacent-posts.d.ts +11 -1
- package/dist/esm/transformations/blog-adjacent-posts.d.ts.map +1 -1
- package/dist/esm/transformations/blog-adjacent-posts.js +14 -1
- package/dist/esm/transformations/blog-adjacent-posts.js.map +1 -1
- package/dist/esm/transformations/blog-comments.d.ts +1 -1
- package/dist/esm/transformations/blog-comments.d.ts.map +1 -1
- package/dist/esm/transformations/blog-comments.js +1 -1
- package/dist/esm/transformations/blog-comments.js.map +1 -1
- package/dist/esm/transformations/blog-post.d.ts +14 -0
- package/dist/esm/transformations/blog-post.d.ts.map +1 -1
- package/dist/esm/transformations/blog-post.js +4 -1
- package/dist/esm/transformations/blog-post.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/heading-anchors.d.ts +32 -8
- package/dist/esm/transformations/heading-anchors.d.ts.map +1 -1
- package/dist/esm/transformations/heading-anchors.js +33 -9
- package/dist/esm/transformations/heading-anchors.js.map +1 -1
- package/dist/esm/transformations/index.d.ts +27 -13
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +72 -16
- package/dist/esm/transformations/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +52 -3
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/count-effect.ts +31 -8
- package/src/fetchers/pg-client-import.ts +16 -0
- package/src/fetchers/postgresql.ts +25 -23
- package/src/fetchers/raw-query.ts +2 -1
- package/src/fetchers/rest-api.ts +3 -1
- package/src/fetchers/teleport.ts +22 -23
- package/src/fetchers/utils/browser-row-policy.ts +10 -20
- package/src/filter-utils.ts +102 -28
- package/src/infinite-scroll.ts +240 -79
- package/src/list-sort-order.ts +34 -0
- package/src/pagination-controls.ts +26 -3
- package/src/pagination-plugin.ts +370 -306
- package/src/product-price-sort.ts +0 -11
- package/src/range-control.ts +207 -0
- package/src/row-order.ts +55 -0
- package/src/search-input.ts +190 -0
- package/src/sort-utils.ts +25 -0
- package/src/transformations/blog-adjacent-posts.ts +89 -3
- package/src/transformations/blog-comments.ts +5 -5
- package/src/transformations/blog-post.ts +64 -13
- package/src/transformations/booking-item.ts +71 -0
- package/src/transformations/heading-anchors.ts +79 -16
- package/src/transformations/index.ts +104 -14
- package/src/utils.ts +74 -3
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { generateBlogPostTransformationCode } from '../src/transformations/blog-post'
|
|
2
|
+
import { generateSharedTransformationCode } from '../src/transformations/shared-utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A post that stores no reading time gets one from its body — its words at
|
|
6
|
+
* about 200 a minute, at least one, as the editor's post form computes it —
|
|
7
|
+
* so a card or post page never prints "min read" with no number before it.
|
|
8
|
+
* The SAME cases as teleport-gui `packages/renderer/src/utils/__tests__/
|
|
9
|
+
* blog-reading-time.test.ts`: the canvas and the site must agree.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const buildPost = (headingAnchors = false): ((record: unknown) => Record<string, unknown>) => {
|
|
13
|
+
const code =
|
|
14
|
+
generateSharedTransformationCode() +
|
|
15
|
+
'\n' +
|
|
16
|
+
generateBlogPostTransformationCode({ headingAnchors })
|
|
17
|
+
return new Function(code + '\nreturn buildBlogPost;')() as (
|
|
18
|
+
record: unknown
|
|
19
|
+
) => Record<string, unknown>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const words = (count: number) => `<p>${Array.from({ length: count }, () => 'word').join(' ')}</p>`
|
|
23
|
+
const POST = { id: 'p1', title: 'Connect a domain', slug: 'connect-a-domain', status: 'published' }
|
|
24
|
+
|
|
25
|
+
describe('a post’s reading time', () => {
|
|
26
|
+
it('is the one the post stores', () => {
|
|
27
|
+
expect(
|
|
28
|
+
buildPost()({ ...POST, reading_time_minutes: 7, content: words(401) }).readingTimeMinutes
|
|
29
|
+
).toBe(7)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('comes from the body when the post stores none', () => {
|
|
33
|
+
expect(buildPost()({ ...POST, content: words(401) }).readingTimeMinutes).toBe(3)
|
|
34
|
+
expect(
|
|
35
|
+
buildPost()({ ...POST, reading_time_minutes: null, content: words(12) }).readingTimeMinutes
|
|
36
|
+
).toBe(1)
|
|
37
|
+
expect(
|
|
38
|
+
buildPost()({
|
|
39
|
+
...POST,
|
|
40
|
+
content: '<h2>Steps</h2><ul><li>Open the panel</li><li>Click Save</li></ul>',
|
|
41
|
+
}).readingTimeMinutes
|
|
42
|
+
).toBe(1)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('counts the words, not the heading anchors a site adds', () => {
|
|
46
|
+
const content = `<h2>One</h2>${words(399)}`
|
|
47
|
+
expect(buildPost(true)({ ...POST, content }).readingTimeMinutes).toBe(2)
|
|
48
|
+
expect(buildPost(false)({ ...POST, content }).readingTimeMinutes).toBe(2)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('is none for a post with no body', () => {
|
|
52
|
+
expect(buildPost()({ ...POST, content: '' }).readingTimeMinutes).toBeNull()
|
|
53
|
+
expect(buildPost()({ ...POST, content: '<p> </p>' }).readingTimeMinutes).toBeNull()
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -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
|
+
})
|
|
@@ -67,7 +67,10 @@ function bootTableRoute(tableName: string, options: BootOptions = {}) {
|
|
|
67
67
|
options.cache
|
|
68
68
|
)
|
|
69
69
|
const code = esm
|
|
70
|
-
.replace(
|
|
70
|
+
.replace(
|
|
71
|
+
"import { Client, types as pgTypes } from 'pg'",
|
|
72
|
+
"const { Client, types: pgTypes } = require('pg')"
|
|
73
|
+
)
|
|
71
74
|
.replace(
|
|
72
75
|
"import { tqWithCache } from '../tq-cache/server'",
|
|
73
76
|
"const { tqWithCache } = require('tq-cache')"
|
|
@@ -233,7 +236,11 @@ describe('blog posts: a visitor reads the published posts, never an author email
|
|
|
233
236
|
it('serves the store everything: the admin, a server segment and getStaticProps', async () => {
|
|
234
237
|
const admin = await posts.run({ session: { role: 'admin' } })
|
|
235
238
|
expect(mainQuery(admin)).not.toContain("status = 'published'")
|
|
236
|
-
|
|
239
|
+
// The post view model carries no email for anyone — a page's data must not
|
|
240
|
+
// ship it (transformations/blog-post.ts); the admin form reads raw rows.
|
|
241
|
+
expect(JSON.stringify(admin.body.data)).not.toContain('ana@example.com')
|
|
242
|
+
const adminRaw = await posts.run({ session: { role: 'admin' }, query: { rawRows: 'true' } })
|
|
243
|
+
expect(JSON.stringify(adminRaw.body.data)).toContain('ana@example.com')
|
|
237
244
|
|
|
238
245
|
const server = await posts.run({ headers: internal })
|
|
239
246
|
expect(mainQuery(server)).not.toContain("status = 'published'")
|
|
@@ -413,7 +420,10 @@ describe('raw-query state routes bind {{Current User.*}} to the session', () =>
|
|
|
413
420
|
paramFields
|
|
414
421
|
)
|
|
415
422
|
const code = esm
|
|
416
|
-
.replace(
|
|
423
|
+
.replace(
|
|
424
|
+
"import { Client, types as pgTypes } from 'pg'",
|
|
425
|
+
"const { Client, types: pgTypes } = require('pg')"
|
|
426
|
+
)
|
|
417
427
|
.replace(/^export default async function handler/m, 'async function handler')
|
|
418
428
|
.concat('\nmodule.exports = { handler: handler }')
|
|
419
429
|
const queries: Array<{ sql: string; params: unknown[] }> = []
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ContentTables, TableAccess } from '@teleporthq/teleport-shared'
|
|
2
|
+
import {
|
|
3
|
+
BROWSER_READABLE_TABLES,
|
|
4
|
+
BROWSER_ROW_POLICIES as DATA_API_ROW_POLICIES,
|
|
5
|
+
} from '../../teleport-plugin-next-workflows/src/data-api-route-generator'
|
|
6
|
+
import {
|
|
7
|
+
BROWSER_ROW_POLICIES,
|
|
8
|
+
isViewDependentTable,
|
|
9
|
+
} from '../src/fetchers/utils/browser-row-policy'
|
|
10
|
+
import {
|
|
11
|
+
buildProductTransformOptions,
|
|
12
|
+
contentSettingsFor,
|
|
13
|
+
detectTransformationType,
|
|
14
|
+
getTransformWrapperCode,
|
|
15
|
+
getTransformationCode,
|
|
16
|
+
} from '../src/transformations'
|
|
17
|
+
import { generateBlogContextFileContent } from '../../teleport-project-generator-next/src/blog/blog-context-generator'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Every content preset in the registry is wired through every generator that
|
|
21
|
+
* tells a content table apart from a custom one. A preset registered in
|
|
22
|
+
* teleport-shared but unknown to one of these would publish its drafts, or
|
|
23
|
+
* serve its rows raw — so each check runs per preset, and a new one fails
|
|
24
|
+
* here before it fails on a site.
|
|
25
|
+
*/
|
|
26
|
+
describe('content preset wiring in the generators', () => {
|
|
27
|
+
it.each(ContentTables.CONTENT_TABLES.map((tables) => [tables.key, tables]))(
|
|
28
|
+
'the "%s" preset is recognised everywhere',
|
|
29
|
+
(_key, tables: ContentTables.ContentTables) => {
|
|
30
|
+
// Visitors are served published rows only, through both route families.
|
|
31
|
+
expect(BROWSER_ROW_POLICIES[tables.posts]).toBe(
|
|
32
|
+
ContentTables.CONTENT_POSTS_VISITOR_ROW_POLICY
|
|
33
|
+
)
|
|
34
|
+
expect(DATA_API_ROW_POLICIES[tables.posts]).toBe(
|
|
35
|
+
ContentTables.CONTENT_POSTS_VISITOR_ROW_POLICY
|
|
36
|
+
)
|
|
37
|
+
expect(BROWSER_READABLE_TABLES).toContain(tables.posts)
|
|
38
|
+
expect(isViewDependentTable(tables.posts, TableAccess.PROTECTED_TABLES)).toBe(true)
|
|
39
|
+
|
|
40
|
+
// The rows go through the post view model, whose SQL names THIS preset's tables.
|
|
41
|
+
expect(detectTransformationType(tables.posts)).toBe('blog-post')
|
|
42
|
+
expect(detectTransformationType(`public.${tables.posts}`)).toBe('blog-post')
|
|
43
|
+
const code = getTransformationCode(tables.posts)
|
|
44
|
+
expect(code).toContain(`SELECT * FROM ${tables.posts} WHERE id = ANY($1)`)
|
|
45
|
+
expect(code).toContain(`SELECT p.* FROM ${tables.posts} p,`)
|
|
46
|
+
if (tables.comments) {
|
|
47
|
+
expect(code).toContain(`FROM ${tables.comments} c`)
|
|
48
|
+
// A browser never lists comments: the table carries the commenters' emails.
|
|
49
|
+
expect(TableAccess.CUSTOMER_RECORD_TABLES).toContain(tables.comments)
|
|
50
|
+
expect(BROWSER_READABLE_TABLES).not.toContain(tables.comments)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
it('leaves a custom table that only sounds like posts alone', () => {
|
|
56
|
+
expect(detectTransformationType('blog_posts')).toBeNull()
|
|
57
|
+
expect(BROWSER_ROW_POLICIES.blog_posts).toBeUndefined()
|
|
58
|
+
expect(BROWSER_READABLE_TABLES).not.toContain('blog_posts')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('bakes each preset\u2019s own settings into its transform, never another\u2019s', () => {
|
|
62
|
+
const blogTaxonomy = [{ id: 'b1', name: 'Blog cat', slug: 'blog-cat' }]
|
|
63
|
+
const helpTaxonomy = [{ id: 'h1', name: 'Help cat', slug: 'help-cat' }]
|
|
64
|
+
const options = buildProductTransformOptions({
|
|
65
|
+
blogSettings: { categories: blogTaxonomy as never, headingAnchors: false, comments: true },
|
|
66
|
+
helpCenterSettings: {
|
|
67
|
+
categories: helpTaxonomy as never,
|
|
68
|
+
headingAnchors: true,
|
|
69
|
+
comments: false,
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
expect(contentSettingsFor(options, 'teleport_blog_posts')).toEqual({
|
|
73
|
+
categories: blogTaxonomy,
|
|
74
|
+
headingAnchors: false,
|
|
75
|
+
comments: true,
|
|
76
|
+
})
|
|
77
|
+
expect(contentSettingsFor(options, 'teleport_help_articles')).toEqual({
|
|
78
|
+
categories: helpTaxonomy,
|
|
79
|
+
headingAnchors: true,
|
|
80
|
+
comments: false,
|
|
81
|
+
})
|
|
82
|
+
expect(contentSettingsFor(options, 'teleport_products')).toEqual({})
|
|
83
|
+
// The Blog's legacy fields still answer for a caller that set only them.
|
|
84
|
+
expect(contentSettingsFor({ blogComments: true }, 'teleport_blog_posts').comments).toBe(true)
|
|
85
|
+
|
|
86
|
+
// The article transform carries the Help Center's taxonomy and anchors, not the Blog's.
|
|
87
|
+
const helpCode = getTransformationCode('teleport_help_articles', options)
|
|
88
|
+
expect(helpCode).toContain('Help cat')
|
|
89
|
+
expect(helpCode).not.toContain('Blog cat')
|
|
90
|
+
expect(helpCode).toContain('var BLOG_HEADING_ANCHORS = true')
|
|
91
|
+
// Comments are a Blog feature: the article wrapper never queries a comments table.
|
|
92
|
+
expect(getTransformWrapperCode('teleport_help_articles', options)).not.toContain(
|
|
93
|
+
'getBlogCommentsMap('
|
|
94
|
+
)
|
|
95
|
+
expect(getTransformWrapperCode('teleport_blog_posts', options)).toContain('getBlogCommentsMap(')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('exposes both taxonomies from the one generated context module', () => {
|
|
99
|
+
const code = generateBlogContextFileContent(
|
|
100
|
+
{ categories: [{ id: 'b1', name: 'Blog cat', slug: 'blog-cat' }] as never },
|
|
101
|
+
{ categories: [{ id: 'h1', name: 'Help cat', slug: 'help-cat' }] as never }
|
|
102
|
+
)
|
|
103
|
+
expect(code).toContain('const BLOG_CATEGORIES = [{"id":"b1"')
|
|
104
|
+
expect(code).toContain('const HELP_CATEGORIES = [{"id":"h1"')
|
|
105
|
+
expect(code).toContain('export const useBlogCategories = ()')
|
|
106
|
+
expect(code).toContain('export const useHelpCategories = ()')
|
|
107
|
+
})
|
|
108
|
+
})
|
|
@@ -335,7 +335,7 @@ describe('generateDataSourceFetcher', () => {
|
|
|
335
335
|
const dataSource = createPostgreSQLDataSource('ds-1')
|
|
336
336
|
const code = generateDataSourceFetcher(dataSource, 'users')
|
|
337
337
|
|
|
338
|
-
expect(code).toContain('import { Client } from')
|
|
338
|
+
expect(code).toContain('import { Client, types as pgTypes } from')
|
|
339
339
|
expect(code).toContain('export default async function handler')
|
|
340
340
|
expect(code).toContain('SELECT * FROM "users"')
|
|
341
341
|
expect(code).toContain('localhost')
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import * as types from '@babel/types'
|
|
2
|
+
import generator from '@babel/generator'
|
|
3
|
+
import {
|
|
4
|
+
ChunkType,
|
|
5
|
+
FileType,
|
|
6
|
+
ComponentStructure,
|
|
7
|
+
ChunkDefinition,
|
|
8
|
+
UIDLDataCacheConfig,
|
|
9
|
+
} from '@teleporthq/teleport-types'
|
|
10
|
+
import { createNextArrayMapperPaginationPlugin } from '../src/pagination-plugin'
|
|
11
|
+
import { readFixedSort } from '../src/sort-utils'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A list whose sort the editor FIXED (a blog listing's newest first: a static
|
|
15
|
+
* column, a static direction) is a static sort. Read as dynamic, it made the
|
|
16
|
+
* list skip the server-side first page — `initialData` is left out for a
|
|
17
|
+
* dynamic sort, since that prefetch ran without one — so the pre-rendered
|
|
18
|
+
* HTML of every blog and help center listing carried no posts, and the
|
|
19
|
+
* browser fetched page 1 a second time. Only a sort bound to state is dynamic.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const CACHE: UIDLDataCacheConfig = {
|
|
23
|
+
enabled: true,
|
|
24
|
+
ttlSeconds: 60,
|
|
25
|
+
client: true,
|
|
26
|
+
server: true,
|
|
27
|
+
versionScope: 'ds1:posts',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const element = (name: string, attributes: types.JSXAttribute[] = []) =>
|
|
31
|
+
types.jsxElement(
|
|
32
|
+
types.jsxOpeningElement(types.jsxIdentifier(name), attributes, true),
|
|
33
|
+
null,
|
|
34
|
+
[],
|
|
35
|
+
true
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
const makeDataProviderJSX = (): types.JSXElement =>
|
|
39
|
+
element('DataProvider', [
|
|
40
|
+
types.jsxAttribute(
|
|
41
|
+
types.jsxIdentifier('name'),
|
|
42
|
+
types.jsxExpressionContainer(types.stringLiteral('items'))
|
|
43
|
+
),
|
|
44
|
+
types.jsxAttribute(
|
|
45
|
+
types.jsxIdentifier('renderSuccess'),
|
|
46
|
+
types.jsxExpressionContainer(
|
|
47
|
+
types.arrowFunctionExpression(
|
|
48
|
+
[types.identifier('items')],
|
|
49
|
+
element('Repeater', [
|
|
50
|
+
types.jsxAttribute(
|
|
51
|
+
types.jsxIdentifier('renderItem'),
|
|
52
|
+
types.jsxExpressionContainer(
|
|
53
|
+
types.arrowFunctionExpression([types.identifier('post')], element('div'))
|
|
54
|
+
)
|
|
55
|
+
),
|
|
56
|
+
])
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
),
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
const makeComponentChunk = (): ChunkDefinition => ({
|
|
63
|
+
name: 'jsx-component',
|
|
64
|
+
type: ChunkType.AST,
|
|
65
|
+
fileType: FileType.JS,
|
|
66
|
+
linkAfter: [],
|
|
67
|
+
content: types.variableDeclaration('const', [
|
|
68
|
+
types.variableDeclarator(
|
|
69
|
+
types.identifier('PostsList'),
|
|
70
|
+
types.arrowFunctionExpression(
|
|
71
|
+
[types.identifier('props')],
|
|
72
|
+
types.blockStatement([types.returnStatement(makeDataProviderJSX())])
|
|
73
|
+
)
|
|
74
|
+
),
|
|
75
|
+
]),
|
|
76
|
+
meta: {},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
type SortKind = 'fixed' | 'state' | 'none'
|
|
80
|
+
|
|
81
|
+
const SORTS: Record<SortKind, Record<string, unknown>> = {
|
|
82
|
+
fixed: {
|
|
83
|
+
sort: { type: 'static', content: 'created_at' },
|
|
84
|
+
sortDirection: { type: 'static', content: 'desc' },
|
|
85
|
+
},
|
|
86
|
+
state: {
|
|
87
|
+
sort: { type: 'expr', content: 'sortBy.split("-")[0]' },
|
|
88
|
+
sortDirection: { type: 'expr', content: 'sortBy.split("-")[1]' },
|
|
89
|
+
},
|
|
90
|
+
none: {},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const runPlugin = async (sort: SortKind, cache?: UIDLDataCacheConfig): Promise<string> => {
|
|
94
|
+
const chunk = makeComponentChunk()
|
|
95
|
+
const structure: ComponentStructure = {
|
|
96
|
+
uidl: {
|
|
97
|
+
name: 'PostsList',
|
|
98
|
+
node: {
|
|
99
|
+
type: 'data-source-list',
|
|
100
|
+
content: {
|
|
101
|
+
renderPropIdentifier: 'items',
|
|
102
|
+
resourceDefinition: {
|
|
103
|
+
dataSourceId: 'ds1',
|
|
104
|
+
tableName: 'posts',
|
|
105
|
+
dataSourceType: 'postgresql',
|
|
106
|
+
},
|
|
107
|
+
resource: { params: { queryColumns: { content: ['title'] } } },
|
|
108
|
+
nodes: {
|
|
109
|
+
success: {
|
|
110
|
+
type: 'cms-list-repeater',
|
|
111
|
+
content: {
|
|
112
|
+
renderPropIdentifier: 'post',
|
|
113
|
+
paginated: true,
|
|
114
|
+
perPage: 20,
|
|
115
|
+
searchEnabled: true,
|
|
116
|
+
searchDebounce: 300,
|
|
117
|
+
nodes: { list: { type: 'element', content: { elementType: 'div' } } },
|
|
118
|
+
...(cache ? { cache } : {}),
|
|
119
|
+
...SORTS[sort],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
chunks: [chunk],
|
|
127
|
+
dependencies: {},
|
|
128
|
+
options: {
|
|
129
|
+
dataSources: {
|
|
130
|
+
ds1: {
|
|
131
|
+
id: 'ds1',
|
|
132
|
+
name: 'Content',
|
|
133
|
+
type: 'postgresql',
|
|
134
|
+
config: { host: 'h', database: 'd', user: 'u', password: 'p' },
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
extractedResources: {},
|
|
138
|
+
},
|
|
139
|
+
} as never
|
|
140
|
+
await createNextArrayMapperPaginationPlugin()(structure)
|
|
141
|
+
return generator(chunk.content as types.Node).code.replace(/\s+/g, ' ')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
describe('a list sort the editor fixed', () => {
|
|
145
|
+
it('keeps the server-side first page and asks for the sort itself', async () => {
|
|
146
|
+
const code = await runPlugin('fixed')
|
|
147
|
+
expect(code).toContain('initialData=')
|
|
148
|
+
expect(code).toContain('sorts: JSON.stringify([{ field: "created_at", order: "desc" }])')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('may peek at the browser cache on mount, as an unsorted list may', async () => {
|
|
152
|
+
expect(await runPlugin('fixed', CACHE)).toContain('ds_0_cached')
|
|
153
|
+
expect(await runPlugin('none', CACHE)).toContain('ds_0_cached')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('leaves a sort bound to state dynamic: no first page from the server, no peek', async () => {
|
|
157
|
+
const code = await runPlugin('state', CACHE)
|
|
158
|
+
expect(code).not.toContain('initialData=')
|
|
159
|
+
expect(code).not.toContain('ds_0_cached')
|
|
160
|
+
expect(code).toContain('field: sortBy.split("-")?.[0]')
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('readFixedSort', () => {
|
|
165
|
+
it('reads a static column and direction, ascending by default', () => {
|
|
166
|
+
expect(
|
|
167
|
+
readFixedSort({ type: 'static', content: 'created_at' }, { type: 'static', content: 'desc' })
|
|
168
|
+
).toEqual({ field: 'created_at', order: 'desc' })
|
|
169
|
+
expect(readFixedSort({ type: 'static', content: 'title' }, undefined)).toEqual({
|
|
170
|
+
field: 'title',
|
|
171
|
+
order: 'asc',
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('refuses anything bound to state, and an empty column', () => {
|
|
176
|
+
expect(readFixedSort({ type: 'expr', content: 'sortBy' }, undefined)).toBeUndefined()
|
|
177
|
+
expect(
|
|
178
|
+
readFixedSort({ type: 'static', content: 'title' }, { type: 'expr', content: 'direction' })
|
|
179
|
+
).toBeUndefined()
|
|
180
|
+
expect(readFixedSort({ type: 'static', content: ' ' }, undefined)).toBeUndefined()
|
|
181
|
+
expect(readFixedSort(undefined, undefined)).toBeUndefined()
|
|
182
|
+
})
|
|
183
|
+
})
|