@financial-times/cp-content-pipeline-schema 3.7.0 → 3.7.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.
- package/CHANGELOG.md +15 -0
- package/lib/model/Image.js +19 -10
- package/lib/model/Image.js.map +1 -1
- package/lib/model/Image.test.js +60 -24
- package/lib/model/Image.test.js.map +1 -1
- package/lib/model/Topper.js +0 -3
- package/lib/model/Topper.js.map +1 -1
- package/lib/model/Topper.test.js +15 -0
- package/lib/model/Topper.test.js.map +1 -1
- package/package.json +1 -1
- package/src/model/Image.test.ts +66 -37
- package/src/model/Image.ts +21 -13
- package/src/model/Topper.test.ts +19 -0
- package/src/model/Topper.ts +0 -4
- package/tsconfig.tsbuildinfo +1 -1
package/src/model/Image.test.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { CAPIImage } from './Image'
|
|
2
|
-
import
|
|
2
|
+
import { jest } from '@jest/globals'
|
|
3
3
|
import type { QueryContext } from '..'
|
|
4
4
|
import { Image } from './schemas/capi/internal-content'
|
|
5
|
+
import { OrigamiImageDataSource } from '../datasources/origami-image'
|
|
5
6
|
import { MAX_IMAGE_WIDTH } from '../helpers/imageService'
|
|
7
|
+
import type { ImageSource } from '../generated'
|
|
6
8
|
|
|
7
9
|
describe('Image', () => {
|
|
8
10
|
const mockImage = {
|
|
@@ -11,8 +13,6 @@ describe('Image', () => {
|
|
|
11
13
|
title: 'title',
|
|
12
14
|
description: 'description',
|
|
13
15
|
binaryUrl: 'cloudfront.com/image',
|
|
14
|
-
pixelWidth: 5000,
|
|
15
|
-
pixelHeight: 1000,
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const graphic = {
|
|
@@ -20,17 +20,34 @@ describe('Image', () => {
|
|
|
20
20
|
type: 'http://www.ft.com/ontology/content/Graphic' as const,
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
const getImageMetadata = jest.fn<OrigamiImageDataSource['getImageMetadata']>()
|
|
24
|
+
|
|
23
25
|
const context = {
|
|
24
|
-
|
|
26
|
+
dataSources: {
|
|
27
|
+
origami: {
|
|
28
|
+
getImageMetadata,
|
|
29
|
+
},
|
|
30
|
+
capi: {},
|
|
31
|
+
vanityUrls: {},
|
|
32
|
+
},
|
|
25
33
|
systemCode: 'image-test',
|
|
26
34
|
} as unknown as QueryContext
|
|
27
35
|
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
getImageMetadata.mockReset()
|
|
38
|
+
})
|
|
39
|
+
|
|
28
40
|
describe('sourceSet', () => {
|
|
29
41
|
describe('An image that is provided with a high resolution', () => {
|
|
30
|
-
|
|
42
|
+
let sourceSet: ImageSource[]
|
|
43
|
+
|
|
44
|
+
beforeAll(async () => {
|
|
45
|
+
getImageMetadata.mockResolvedValue({ width: 5000, height: 1000 })
|
|
31
46
|
const model = new CAPIImage(mockImage, context)
|
|
32
|
-
|
|
47
|
+
sourceSet = await model.sourceSet({ width: 1000 })
|
|
48
|
+
})
|
|
33
49
|
|
|
50
|
+
it('transforms the URL to use the Origami Image Service', () => {
|
|
34
51
|
expect(
|
|
35
52
|
sourceSet.every((source) =>
|
|
36
53
|
source.url.startsWith(
|
|
@@ -40,10 +57,7 @@ describe('Image', () => {
|
|
|
40
57
|
).toBeTruthy()
|
|
41
58
|
})
|
|
42
59
|
|
|
43
|
-
it('resizes the image to the requested width',
|
|
44
|
-
const model = new CAPIImage(mockImage, context)
|
|
45
|
-
const sourceSet = await model.sourceSet({ width: 1000 })
|
|
46
|
-
|
|
60
|
+
it('resizes the image to the requested width', () => {
|
|
47
61
|
expect(
|
|
48
62
|
sourceSet.every(
|
|
49
63
|
(source) =>
|
|
@@ -52,9 +66,7 @@ describe('Image', () => {
|
|
|
52
66
|
).toBeTruthy()
|
|
53
67
|
})
|
|
54
68
|
|
|
55
|
-
it('includes sourceSet for all the possible resolutions we can display the image at, given the requested width and the original source width',
|
|
56
|
-
const model = new CAPIImage(mockImage, context)
|
|
57
|
-
const sourceSet = await model.sourceSet({ width: 1000 })
|
|
69
|
+
it('includes sourceSet for all the possible resolutions we can display the image at, given the requested width and the original source width', () => {
|
|
58
70
|
expect(sourceSet.length).toEqual(5)
|
|
59
71
|
expect(sourceSet[0]?.dpr).toEqual(1)
|
|
60
72
|
expect(sourceSet[0]?.url).toMatch(/dpr=1/)
|
|
@@ -65,10 +77,8 @@ describe('Image', () => {
|
|
|
65
77
|
|
|
66
78
|
describe("An image that that isn't wide enough to generate high-resolution sourceSet for", () => {
|
|
67
79
|
it('returns an array with a single resolution iamge service UR', async () => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
context
|
|
71
|
-
)
|
|
80
|
+
getImageMetadata.mockResolvedValue({ width: 1500, height: 500 })
|
|
81
|
+
const model = new CAPIImage(mockImage, context)
|
|
72
82
|
const sourceSet = await model.sourceSet({ width: 1000 })
|
|
73
83
|
expect(sourceSet.length).toEqual(1)
|
|
74
84
|
expect(sourceSet[0]).toEqual({
|
|
@@ -81,10 +91,8 @@ describe('Image', () => {
|
|
|
81
91
|
|
|
82
92
|
describe('An image that that is smaller than the width that was requested', () => {
|
|
83
93
|
it('returns the image at the same width as the original', async () => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
context
|
|
87
|
-
)
|
|
94
|
+
getImageMetadata.mockResolvedValue({ width: 500, height: 500 })
|
|
95
|
+
const model = new CAPIImage(mockImage, context)
|
|
88
96
|
const sourceSet = await model.sourceSet({ width: 1000 })
|
|
89
97
|
expect(sourceSet.length).toEqual(1)
|
|
90
98
|
expect(sourceSet[0]).toEqual({
|
|
@@ -97,10 +105,8 @@ describe('Image', () => {
|
|
|
97
105
|
|
|
98
106
|
describe('An image that that is bigger than the maximum width that was requested', () => {
|
|
99
107
|
it('returns the image at the same width as the maximum width', async () => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
context
|
|
103
|
-
)
|
|
108
|
+
getImageMetadata.mockResolvedValue({ width: 4000, height: 4000 })
|
|
109
|
+
const model = new CAPIImage(mockImage, context)
|
|
104
110
|
const sourceSet = await model.sourceSet({ width: MAX_IMAGE_WIDTH })
|
|
105
111
|
expect(sourceSet.length).toEqual(1)
|
|
106
112
|
expect(sourceSet[0]?.width).toEqual(MAX_IMAGE_WIDTH)
|
|
@@ -109,10 +115,8 @@ describe('Image', () => {
|
|
|
109
115
|
|
|
110
116
|
describe('A large image with the maxDpr argumet passed', () => {
|
|
111
117
|
it('only returns sourceSet up to and including the maxDpr', async () => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
context
|
|
115
|
-
)
|
|
118
|
+
getImageMetadata.mockResolvedValue({ width: 5000, height: 500 })
|
|
119
|
+
const model = new CAPIImage(mockImage, context)
|
|
116
120
|
const sourceSet = await model.sourceSet({ width: 1000, maxDpr: 2 })
|
|
117
121
|
expect(sourceSet.length).toEqual(2)
|
|
118
122
|
expect(sourceSet[0]?.dpr).toEqual(1)
|
|
@@ -133,6 +137,32 @@ describe('Image', () => {
|
|
|
133
137
|
await expect(
|
|
134
138
|
model.sourceSet({ width: 1000, maxDpr: 2 })
|
|
135
139
|
).rejects.toThrow('not-a-uuid is not a valid Content API Image ID')
|
|
140
|
+
expect(getImageMetadata).not.toHaveBeenCalled()
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
describe('When the image service fails to return metadata', () => {
|
|
145
|
+
let sourceSet: ImageSource[]
|
|
146
|
+
beforeEach(async () => {
|
|
147
|
+
getImageMetadata.mockRejectedValue(null)
|
|
148
|
+
const model = new CAPIImage(mockImage, context)
|
|
149
|
+
sourceSet = await model.sourceSet({ width: 3000 })
|
|
150
|
+
})
|
|
151
|
+
it('returns an image service URL with the requested width and max DPR 2', () => {
|
|
152
|
+
expect(sourceSet).toMatchInlineSnapshot(`
|
|
153
|
+
Array [
|
|
154
|
+
Object {
|
|
155
|
+
"dpr": 1,
|
|
156
|
+
"url": "https://www.ft.com/__origami/service/image/v2/images/raw/ftcms%3A00000000-0000-0000-0000-000000000001?source=image-test&fit=scale-down&quality=highest&width=3000&dpr=1",
|
|
157
|
+
"width": 3000,
|
|
158
|
+
},
|
|
159
|
+
Object {
|
|
160
|
+
"dpr": 2,
|
|
161
|
+
"url": "https://www.ft.com/__origami/service/image/v2/images/raw/ftcms%3A00000000-0000-0000-0000-000000000001?source=image-test&fit=scale-down&quality=highest&width=3000&dpr=2",
|
|
162
|
+
"width": 3000,
|
|
163
|
+
},
|
|
164
|
+
]
|
|
165
|
+
`)
|
|
136
166
|
})
|
|
137
167
|
})
|
|
138
168
|
})
|
|
@@ -211,18 +241,17 @@ describe('Image', () => {
|
|
|
211
241
|
})
|
|
212
242
|
|
|
213
243
|
describe('width/height', () => {
|
|
214
|
-
it('returns the original width and height from
|
|
244
|
+
it('returns the original width and height from the image service metadata', async () => {
|
|
245
|
+
getImageMetadata.mockResolvedValue({ width: 100, height: 200 })
|
|
215
246
|
const model = new CAPIImage(mockImage, context)
|
|
216
247
|
const { width, height } = (await model.dimensions()) || {}
|
|
217
|
-
expect(width).toEqual(
|
|
218
|
-
expect(height).toEqual(
|
|
248
|
+
expect(width).toEqual(100)
|
|
249
|
+
expect(height).toEqual(200)
|
|
219
250
|
})
|
|
220
251
|
|
|
221
|
-
it('returns null if
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
context
|
|
225
|
-
)
|
|
252
|
+
it('returns null if the image service metadata call fails', async () => {
|
|
253
|
+
getImageMetadata.mockRejectedValue(null)
|
|
254
|
+
const model = new CAPIImage(mockImage, context)
|
|
226
255
|
const dimensions = await model.dimensions()
|
|
227
256
|
expect(dimensions).toEqual(null)
|
|
228
257
|
})
|
package/src/model/Image.ts
CHANGED
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
validLiteralUnionValue,
|
|
12
12
|
} from '../resolvers/literal-union'
|
|
13
13
|
import { ImageFormat, ImageType } from '../resolvers/scalars'
|
|
14
|
-
import
|
|
14
|
+
import isError from '../helpers/isError'
|
|
15
|
+
import { BaseError, OperationalError } from '@dotcom-reliability-kit/errors'
|
|
15
16
|
import type { ImageSource } from '../generated'
|
|
16
17
|
|
|
17
18
|
export type ImageSourceArgs = {
|
|
@@ -156,18 +157,25 @@ export class CAPIImage implements Image {
|
|
|
156
157
|
height: this.capiImage.pixelHeight,
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
160
|
+
try {
|
|
161
|
+
const imageMetadata = await this.#dataSources.origami.getImageMetadata(
|
|
162
|
+
this.capiImage.binaryUrl
|
|
163
|
+
)
|
|
164
|
+
return imageMetadata
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (isError(error)) {
|
|
167
|
+
this.context.logger.warn({
|
|
168
|
+
event: 'RECOVERABLE_ERROR',
|
|
169
|
+
error: new OperationalError({
|
|
170
|
+
code: 'IMAGE_DIMENSIONS_ERROR',
|
|
171
|
+
message: `Failed to get dimensions for ${this.capiImage.binaryUrl}`,
|
|
172
|
+
url: this.capiImage.binaryUrl,
|
|
173
|
+
cause: error,
|
|
174
|
+
}),
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
return null
|
|
178
|
+
}
|
|
171
179
|
}
|
|
172
180
|
|
|
173
181
|
credit() {
|
package/src/model/Topper.test.ts
CHANGED
|
@@ -95,6 +95,25 @@ describe('produces the correct types', () => {
|
|
|
95
95
|
expect(desiredColour).toEqual(actualColour)
|
|
96
96
|
})
|
|
97
97
|
|
|
98
|
+
it('full bleed left topper', () => {
|
|
99
|
+
const splitTopper = {
|
|
100
|
+
headline: '',
|
|
101
|
+
standfirst: '',
|
|
102
|
+
backgroundColour: 'auto',
|
|
103
|
+
layout: 'full-bleed-left',
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const clonedBase = cloneDeep(baseCapiObject)
|
|
107
|
+
clonedBase.topper = splitTopper
|
|
108
|
+
const capiResponse = new CapiResponse(clonedBase, context)
|
|
109
|
+
const topper = new Topper(capiResponse, context)
|
|
110
|
+
|
|
111
|
+
const desired = 'FullBleedTopper'
|
|
112
|
+
const actual = topper.type()
|
|
113
|
+
|
|
114
|
+
expect(actual).toEqual(desired)
|
|
115
|
+
})
|
|
116
|
+
|
|
98
117
|
it('opinion topper, is normal headline, sky background', () => {
|
|
99
118
|
const clonedBase = cloneDeep(baseCapiObject)
|
|
100
119
|
clonedBase.annotations.push({
|
package/src/model/Topper.ts
CHANGED
|
@@ -79,10 +79,6 @@ export class Topper {
|
|
|
79
79
|
return 'SplitTextTopper'
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
if (this.capiResponse.topper()?.layout === 'full-bleed-image-left') {
|
|
83
|
-
return 'SplitTextTopper'
|
|
84
|
-
}
|
|
85
|
-
|
|
86
82
|
if (this.capiResponse.topper()?.layout === 'flourish') {
|
|
87
83
|
return 'TopperWithFlourish'
|
|
88
84
|
}
|