@financial-times/dotcom-server-handlebars 7.3.0 → 7.3.2

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 (33) hide show
  1. package/dist/node/PageKitHandlebars.d.ts +46 -0
  2. package/dist/node/findPartialFiles.d.ts +2 -0
  3. package/dist/node/helpers/array.d.ts +1 -0
  4. package/dist/node/helpers/capture.d.ts +2 -0
  5. package/dist/node/helpers/concat.d.ts +1 -0
  6. package/dist/node/helpers/dateformat.d.ts +1 -0
  7. package/dist/node/helpers/encode.d.ts +1 -0
  8. package/dist/node/helpers/ifAll.d.ts +1 -0
  9. package/dist/node/helpers/ifEquals.d.ts +1 -0
  10. package/dist/node/helpers/ifEqualsSome.d.ts +1 -0
  11. package/dist/node/helpers/ifSome.d.ts +1 -0
  12. package/dist/node/helpers/json.d.ts +1 -0
  13. package/dist/node/helpers/renderReactComponent.d.ts +3 -0
  14. package/dist/node/helpers/resize.d.ts +1 -0
  15. package/dist/node/helpers/slice.d.ts +1 -0
  16. package/dist/node/helpers/unlessAll.d.ts +1 -0
  17. package/dist/node/helpers/unlessEquals.d.ts +1 -0
  18. package/dist/node/helpers/unlessSome.d.ts +1 -0
  19. package/dist/node/helpers.d.ts +16 -0
  20. package/dist/node/index.d.ts +3 -0
  21. package/dist/node/loadFileContents.d.ts +1 -0
  22. package/dist/tsconfig.tsbuildinfo +3320 -0
  23. package/package.json +7 -4
  24. package/src/__test__/HandlebarsRenderer.spec.ts +99 -0
  25. package/src/__test__/__fixtures__/components/DefaultExportComponentCJS.js +5 -0
  26. package/src/__test__/__fixtures__/components/DefaultExportComponentES.tsx +5 -0
  27. package/src/__test__/__fixtures__/components/NamedExportComponentCJS.js +5 -0
  28. package/src/__test__/__fixtures__/components/NamedExportComponentES.tsx +5 -0
  29. package/src/__test__/__fixtures__/views/partials/content.hbs +1 -0
  30. package/src/__test__/__fixtures__/views/partials/partial.hbs +1 -0
  31. package/src/__test__/__fixtures__/views/partials/wrapper.hbs +3 -0
  32. package/src/__test__/__fixtures__/views/view.hbs +4 -0
  33. package/src/__test__/helpers.spec.ts +341 -0
package/package.json CHANGED
@@ -1,16 +1,15 @@
1
1
  {
2
2
  "name": "@financial-times/dotcom-server-handlebars",
3
- "version": "7.3.0",
3
+ "version": "7.3.2",
4
4
  "description": "",
5
5
  "main": "dist/node/index.js",
6
6
  "types": "src/index.ts",
7
7
  "scripts": {
8
8
  "test": "echo \"Error: no test specified\" && exit 1",
9
- "tsc": "../../node_modules/.bin/tsc --incremental",
10
9
  "clean": "npm run clean:dist && npm run clean:node_modules",
11
10
  "clean:dist": "rm -rf dist",
12
11
  "clean:node_modules": "rm -rf node_modules",
13
- "build:node": "npm run tsc -- --module commonjs --outDir ./dist/node",
12
+ "build:node": "tsc",
14
13
  "build": "npm run build:node",
15
14
  "dev": "npm run build:node -- --watch",
16
15
  "preinstall": "[ \"$INIT_CWD\" != \"$PWD\" ] || npm_config_yes=true npx check-engine"
@@ -31,6 +30,10 @@
31
30
  "node": ">= 14.0.0",
32
31
  "npm": "7.x || 8.x"
33
32
  },
33
+ "files": [
34
+ "dist/",
35
+ "src/"
36
+ ],
34
37
  "repository": {
35
38
  "type": "git",
36
39
  "repository": "https://github.com/Financial-Times/dotcom-page-kit.git",
@@ -43,4 +46,4 @@
43
46
  "devDependencies": {
44
47
  "check-engine": "^1.10.1"
45
48
  }
46
- }
49
+ }
@@ -0,0 +1,99 @@
1
+ import path from 'path'
2
+ import { PageKitHandlebars as Subject } from '../PageKitHandlebars'
3
+
4
+ // NOTE: Tests are run from the repository root directory so we need to set the CWD
5
+ const root = path.join(__dirname, '__fixtures__')
6
+ const view = path.resolve(root, 'views/view.hbs')
7
+
8
+ describe('dotcom-server-handlebars/src/PageKitHandlebars', () => {
9
+ let instance
10
+
11
+ beforeEach(() => {
12
+ instance = new Subject({
13
+ rootDirectory: root,
14
+ cache: true
15
+ })
16
+ })
17
+
18
+ describe('.loadPartials()', () => {
19
+ let result
20
+
21
+ beforeEach(() => {
22
+ result = instance.loadPartials()
23
+ })
24
+
25
+ it('loads and compiles partial templates from disk', () => {
26
+ const values = Object.values(result)
27
+
28
+ expect(values.length).toBe(3)
29
+
30
+ values.forEach((template) => {
31
+ expect(template).toEqual(expect.any(Function))
32
+ })
33
+ })
34
+
35
+ it('caches each compiled partial template', () => {
36
+ const keys = Object.keys(result)
37
+
38
+ keys.forEach((key) => {
39
+ const filename = path.join(root, `views/partials/${key}.hbs`)
40
+ expect(instance.cache.has(filename)).toBe(true)
41
+ })
42
+ })
43
+ })
44
+
45
+ describe('.loadTemplate()', () => {
46
+ let result
47
+
48
+ beforeEach(() => {
49
+ result = instance.loadTemplate(view)
50
+ })
51
+
52
+ it('loads and compiles a template from disk', () => {
53
+ expect(result).toEqual(expect.any(Function))
54
+ })
55
+
56
+ it('caches the compiled template', () => {
57
+ expect(instance.cache.has(view)).toBe(true)
58
+ })
59
+ })
60
+
61
+ describe('.render()', () => {
62
+ const templateContext = { title: 'Hello World', aside: 'Lorem ipsum' }
63
+
64
+ it('can render a template from disk', () => {
65
+ const result = instance.render(view, templateContext)
66
+
67
+ expect(result).toContain('<h1>Hello World</h1>')
68
+ expect(result).toContain('<aside>Lorem ipsum</aside>')
69
+ expect(result).toMatch(/<main>.+<\/main>/s)
70
+ })
71
+
72
+ it('can render a given template function', () => {
73
+ const template = instance.loadTemplate(view)
74
+ const result = instance.render(template, templateContext)
75
+
76
+ expect(result).toContain('<h1>Hello World</h1>')
77
+ expect(result).toContain('<aside>Lorem ipsum</aside>')
78
+ expect(result).toMatch(/<main>.+<\/main>/s)
79
+ })
80
+ })
81
+
82
+ describe('.renderView()', () => {
83
+ it('can render a template and fire a callback with the result', () => {
84
+ return new Promise((done) => {
85
+ const templateContext = { title: 'Hello World', aside: 'Lorem ipsum' }
86
+
87
+ instance.renderView(view, templateContext, (error, result) => {
88
+ expect(error).toBeNull()
89
+
90
+ expect(result).toContain('<h1>Hello World</h1>')
91
+ expect(result).toContain('<aside>Lorem ipsum</aside>')
92
+ expect(result).toMatch(/<main>.+<\/main>/s)
93
+
94
+ done()
95
+ })
96
+ })
97
+ })
98
+ })
99
+ })
@@ -0,0 +1,5 @@
1
+ const React = require('react')
2
+
3
+ module.exports = function ({ text }) {
4
+ return React.createElement('div', null, text)
5
+ }
@@ -0,0 +1,5 @@
1
+ import React from 'react'
2
+
3
+ export default function ({ text }) {
4
+ return <div>{text}</div>
5
+ }
@@ -0,0 +1,5 @@
1
+ const React = require('react')
2
+
3
+ exports.Component = function ({ text }) {
4
+ return React.createElement('div', null, text)
5
+ }
@@ -0,0 +1,5 @@
1
+ import React from 'react'
2
+
3
+ export function Component({ text }) {
4
+ return <div>{text}</div>
5
+ }
@@ -0,0 +1 @@
1
+ <h1>{{title}}</h1>
@@ -0,0 +1 @@
1
+ <aside>{{aside}}</aside>
@@ -0,0 +1,3 @@
1
+ <main>
2
+ {{> @partial-block }}
3
+ </main>
@@ -0,0 +1,4 @@
1
+ {{#> wrapper }}
2
+ {{>content}}
3
+ {{>partial}}
4
+ {{/wrapper}}
@@ -0,0 +1,341 @@
1
+ import { compile } from 'handlebars'
2
+ import * as helpers from '../helpers'
3
+
4
+ describe('dotcom-server-handlebars/src/helpers', () => {
5
+ describe('block helpers', () => {
6
+ describe('#capture', () => {
7
+ it('captures the string and assigns it to a variable', () => {
8
+ const template = compile('{{#capture "myOutput"}}Hello, World!{{/capture}}')
9
+ const templateData = {} as { [key: string]: any }
10
+ const result = template(templateData, { helpers })
11
+
12
+ expect(templateData.myOutput).toBe('Hello, World!')
13
+ expect(result).toBe('')
14
+ })
15
+ })
16
+
17
+ describe('#dateformat', () => {
18
+ const date = new Date('2019-04-10 13:40:21 GMT+0100 (BST)')
19
+
20
+ it('formats as ISO date time by default', () => {
21
+ const template = compile('{{#dateformat}}{{date}}{{/dateformat}}')
22
+ const result = template({ date }, { helpers })
23
+
24
+ expect(result).toBe('2019-04-10T12:40:21Z')
25
+ })
26
+
27
+ it('formats using the given format', () => {
28
+ const template = compile('{{#dateformat "dddd, mmmm d, yyyy"}}{{date}}{{/dateformat}}')
29
+ const result = template({ date }, { helpers })
30
+
31
+ expect(result).toBe('Wednesday, April 10, 2019')
32
+ })
33
+
34
+ it('throws if the incorrect number of parameters are provided', () => {
35
+ const template = compile('{{#dateformat "" ""}}{{date}}{{/dateformat}}')
36
+ expect(() => template({}, { helpers })).toThrow()
37
+ })
38
+ })
39
+
40
+ describe('#ifAll', () => {
41
+ const template = compile('{{#ifAll foo bar baz}}yes{{else}}no{{/ifAll}}')
42
+
43
+ it('outputs the contents when all parameters are truthy', () => {
44
+ const result = template({ foo: 123, bar: 'abc', baz: true }, { helpers })
45
+ expect(result).toBe('yes')
46
+ })
47
+
48
+ it('does not output the contents if any parameters are falsy', () => {
49
+ const result = template({ foo: 123, bar: 'abc', baz: false }, { helpers })
50
+ expect(result).toBe('no')
51
+ })
52
+
53
+ it('throws if the incorrect number of parameters are provided', () => {
54
+ const template = compile('{{#ifSome}}yes{{/ifSome}}')
55
+ expect(() => template({}, { helpers })).toThrow()
56
+ })
57
+ })
58
+
59
+ describe('#ifEquals', () => {
60
+ const template = compile('{{#ifEquals foo bar}}yes{{else}}no{{/ifEquals}}')
61
+
62
+ it('outputs the contents parameters are strictly equal', () => {
63
+ const result = template({ foo: true, bar: true }, { helpers })
64
+ expect(result).toBe('yes')
65
+ })
66
+
67
+ it('does not output the contents if any parameter is not strictly equal', () => {
68
+ const result = template({ foo: true, bar: null }, { helpers })
69
+ expect(result).toBe('no')
70
+ })
71
+
72
+ it('throws if the incorrect number of parameters are provided', () => {
73
+ const template = compile('{{#ifEquals}}yes{{/ifEquals}}')
74
+ expect(() => template({}, { helpers })).toThrow()
75
+ })
76
+ })
77
+
78
+ describe('#ifEqualsSome', () => {
79
+ const template = compile('{{#ifEqualsSome foo bar baz}}yes{{else}}no{{/ifEqualsSome}}')
80
+
81
+ it('outputs the contents if parameters are strictly equal', () => {
82
+ const result = template({ foo: true, bar: true, baz: true }, { helpers })
83
+ expect(result).toBe('yes')
84
+ })
85
+
86
+ it('outputs true if first parameter is equal', () => {
87
+ const result = template({ foo: true, bar: false, baz: true }, { helpers })
88
+ expect(result).toBe('yes')
89
+ })
90
+
91
+ it('outputs true if second parameter is equal', () => {
92
+ const result = template({ foo: true, bar: true, baz: false }, { helpers })
93
+ expect(result).toBe('yes')
94
+ })
95
+
96
+ it('outputs false if neither parameter is equal', () => {
97
+ const result = template({ foo: true, bar: false, baz: false }, { helpers })
98
+ expect(result).toBe('no')
99
+ })
100
+
101
+ it('throws if the incorrect number of parameters are provided', () => {
102
+ const template = compile('{{#ifEqualsSome}}yes{{/ifEqualsSome}}')
103
+ expect(() => template({}, { helpers })).toThrow()
104
+ })
105
+ })
106
+
107
+ describe('#ifSome', () => {
108
+ const template = compile('{{#ifSome foo bar baz}}yes{{else}}no{{/ifSome}}')
109
+
110
+ it('outputs the contents if at least one parameter is truthy', () => {
111
+ const result = template({ foo: 123, bar: '', baz: false }, { helpers })
112
+ expect(result).toBe('yes')
113
+ })
114
+
115
+ it('does not output the contents if all parameters are falsy', () => {
116
+ const result = template({ foo: 0, bar: '', baz: false }, { helpers })
117
+ expect(result).toBe('no')
118
+ })
119
+
120
+ it('throws if the incorrect number of parameters are provided', () => {
121
+ const template = compile('{{#ifSome}}yes{{/ifSome}}')
122
+ expect(() => template({}, { helpers })).toThrow()
123
+ })
124
+ })
125
+
126
+ describe('#renderReactComponent', () => {
127
+ it('renders the specified component (exported as a default CJS Module export) from a local path', () => {
128
+ const template = compile(`{{{renderReactComponent
129
+ localPath="packages/dotcom-server-handlebars/src/__test__/__fixtures__/components/DefaultExportComponentCJS"
130
+ text="foo"
131
+ }}}`)
132
+ const result = template({}, { helpers })
133
+
134
+ expect(result).toBe('<div>foo</div>')
135
+ })
136
+
137
+ it('renders the specified component (exported as a default ES Module export) from a local path', () => {
138
+ const template = compile(`{{{renderReactComponent
139
+ localPath="packages/dotcom-server-handlebars/src/__test__/__fixtures__/components/DefaultExportComponentES"
140
+ text="bar"
141
+ }}}`)
142
+ const result = template({}, { helpers })
143
+
144
+ expect(result).toBe('<div>bar</div>')
145
+ })
146
+
147
+ it('renders the specified component (exported as a named CJS Module export) from a local path', () => {
148
+ const template = compile(`{{{renderReactComponent
149
+ localPath="packages/dotcom-server-handlebars/src/__test__/__fixtures__/components/NamedExportComponentCJS"
150
+ namedExport="Component"
151
+ text="baz"
152
+ }}}`)
153
+ const result = template({}, { helpers })
154
+
155
+ expect(result).toBe('<div>baz</div>')
156
+ })
157
+
158
+ it('renders the specified component (exported as a named ES Module export) from a local path', () => {
159
+ const template = compile(`{{{renderReactComponent
160
+ localPath="packages/dotcom-server-handlebars/src/__test__/__fixtures__/components/NamedExportComponentES"
161
+ namedExport="Component"
162
+ text="qux"
163
+ }}}`)
164
+ const result = template({}, { helpers })
165
+
166
+ expect(result).toBe('<div>qux</div>')
167
+ })
168
+
169
+ it('throws if mandatory parameters are not provided', () => {
170
+ const template = compile('{{{renderReactComponent}}}')
171
+ expect(() => template({}, { helpers })).toThrow()
172
+ })
173
+ })
174
+
175
+ describe('#resize', () => {
176
+ it('formats the image as an image service URL', () => {
177
+ const template = compile('{{#resize 640}}http://website.com/picture.jpg{{/resize}}')
178
+ const result = template({}, { helpers })
179
+
180
+ expect(result).toBe(
181
+ 'https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fwebsite.com%2Fpicture.jpg?width=640&source=next&fit=scale-down'
182
+ )
183
+ })
184
+
185
+ it('accepts named parameters', () => {
186
+ const template = compile('{{#resize 640 fit="contain"}}http://website.com/picture.jpg{{/resize}}')
187
+ const result = template({}, { helpers })
188
+
189
+ expect(result).toBe(
190
+ 'https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fwebsite.com%2Fpicture.jpg?width=640&source=next&fit=contain'
191
+ )
192
+ })
193
+
194
+ it('throws if the incorrect number of parameters are provided', () => {
195
+ const template = compile('{{#resize}}http://website.com/picture.jpg{{/resize}}')
196
+ expect(() => template({}, { helpers })).toThrow()
197
+ })
198
+ })
199
+
200
+ describe('#slice', () => {
201
+ it('slices a given array and iterates over the new array', () => {
202
+ const template = compile('{{#slice items offset="2" limit="3"}}{{this}}{{/slice}}')
203
+ const result = template({ items: [1, 2, 3, 4, 5, 6] }, { helpers })
204
+
205
+ expect(result).toBe('345')
206
+ })
207
+
208
+ it('throws if the incorrect number of parameters are provided', () => {
209
+ const template = compile('{{#slice offset="2" limit="3"}}{{this}}{{/slice}}')
210
+ expect(() => template({}, { helpers })).toThrow()
211
+ })
212
+ })
213
+
214
+ describe('#unlessAll', () => {
215
+ const template = compile('{{#unlessAll foo bar baz}}yes{{else}}no{{/unlessAll}}')
216
+
217
+ it('outputs the contents if all parameters are falsy', () => {
218
+ const result = template({ foo: 0, bar: '', baz: false }, { helpers })
219
+ expect(result).toBe('yes')
220
+ })
221
+
222
+ it('does not output the contents if any parameters are truthy', () => {
223
+ const result = template({ foo: 0, bar: '', baz: true }, { helpers })
224
+ expect(result).toBe('no')
225
+ })
226
+
227
+ it('throws if the incorrect number of parameters are provided', () => {
228
+ const template = compile('{{#unlessAll}}yes{{/unlessAll}}')
229
+ expect(() => template({}, { helpers })).toThrow()
230
+ })
231
+ })
232
+
233
+ describe('#unlessEquals', () => {
234
+ const template = compile('{{#unlessEquals foo bar}}yes{{else}}no{{/unlessEquals}}')
235
+
236
+ it('outputs the contents if all parameters are falsy', () => {
237
+ const result = template({ foo: true, bar: false }, { helpers })
238
+ expect(result).toBe('yes')
239
+ })
240
+
241
+ it('does not output the contents if any parameters are truthy', () => {
242
+ const result = template({ foo: true, bar: true }, { helpers })
243
+ expect(result).toBe('no')
244
+ })
245
+
246
+ it('throws if the incorrect number of parameters are provided', () => {
247
+ const template = compile('{{#unlessEquals}}yes{{/unlessEquals}}')
248
+ expect(() => template({}, { helpers })).toThrow()
249
+ })
250
+ })
251
+
252
+ describe('#unlessSome', () => {
253
+ const template = compile('{{#unlessSome foo bar baz}}yes{{else}}no{{/unlessSome}}')
254
+
255
+ it('outputs the contents if any parameters are falsy', () => {
256
+ const result = template({ foo: 1, bar: 'abc', baz: false }, { helpers })
257
+ expect(result).toBe('yes')
258
+ })
259
+
260
+ it('does not output the contents if all parameters are truthy', () => {
261
+ const result = template({ foo: 1, bar: 'abc', baz: true }, { helpers })
262
+ expect(result).toBe('no')
263
+ })
264
+
265
+ it('throws if the incorrect number of parameters are provided', () => {
266
+ const template = compile('{{#unlessSome}}yes{{/unlessSome}}')
267
+ expect(() => template({}, { helpers })).toThrow()
268
+ })
269
+ })
270
+ })
271
+
272
+ describe('inline helpers', () => {
273
+ describe('array', () => {
274
+ it('converts all parameters into one array', () => {
275
+ const template = compile('{{array foo bar baz}}')
276
+ const result = template({ foo: 1, bar: 2, baz: 3 }, { helpers })
277
+
278
+ expect(result).toBe('1,2,3')
279
+ })
280
+
281
+ it('throws if the incorrect number of parameters are provided', () => {
282
+ const template = compile('{{array}}')
283
+ expect(() => template({}, { helpers })).toThrow()
284
+ })
285
+ })
286
+
287
+ describe('concat', () => {
288
+ it('concatenates all parameters into one string', () => {
289
+ const template = compile('{{concat "Welcome to " place ", " name}}')
290
+ const result = template({ place: 'Hell', name: 'human' }, { helpers })
291
+
292
+ expect(result).toBe('Welcome to Hell, human')
293
+ })
294
+
295
+ it('throws if the incorrect number of parameters are provided', () => {
296
+ const template = compile('{{concat}}')
297
+ expect(() => template({}, { helpers })).toThrow()
298
+ })
299
+ })
300
+
301
+ describe('encode', () => {
302
+ it('encodes the parameter as a URI component', () => {
303
+ const template = compile('{{{encode title}}}')
304
+ const result = template({ title: 'http://www.foo.com?bar=baz&qux=«»' }, { helpers })
305
+
306
+ expect(result).toBe('http%3A%2F%2Fwww.foo.com%3Fbar%3Dbaz%26qux%3D%C2%AB%C2%BB')
307
+ })
308
+
309
+ it('encodes the parameter as a complete URI', () => {
310
+ const template = compile('{{{encode title mode="uri"}}}')
311
+ const result = template({ title: 'http://www.foo.com?bar=baz&qux=«»' }, { helpers })
312
+
313
+ expect(result).toBe('http://www.foo.com?bar=baz&qux=%C2%AB%C2%BB')
314
+ })
315
+
316
+ it('throws if the incorrect number of parameters are provided', () => {
317
+ const template = compile('{{encode}}')
318
+ expect(() => template({}, { helpers })).toThrow()
319
+ })
320
+ })
321
+
322
+ describe('json', () => {
323
+ it('stringifies the parameter', () => {
324
+ const template = compile('{{{json data}}}')
325
+ const result = template({ data: { foo: 'bar', baz: 123, qux: true } }, { helpers })
326
+
327
+ expect(result).toBe('{"foo":"bar","baz":123,"qux":true}')
328
+ })
329
+
330
+ it('throws if the incorrect number of parameters are provided', () => {
331
+ const template = compile('{{json}}')
332
+ expect(() => template({}, { helpers })).toThrow()
333
+ })
334
+
335
+ it('throws if the user tries to output the @root context', () => {
336
+ const template = compile('{{json data}}')
337
+ expect(() => template({ data: { _locals: true } }, { helpers })).toThrow()
338
+ })
339
+ })
340
+ })
341
+ })