@financial-times/cp-content-pipeline-schema 2.9.1 → 2.9.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.
@@ -141,7 +141,7 @@ function getContentType(
141
141
  const TYPE_REGEX = /https?:\/\/www.ft.com\/ontology\/content\//
142
142
  const useType =
143
143
  'type' in content && content.type ? content.type : content.types[0]
144
- const type = useType.replace(TYPE_REGEX, '')
144
+ const type = useType?.replace(TYPE_REGEX, '')
145
145
 
146
146
  if (validate && !validLiteralUnionValue(type, ContentType.values)) {
147
147
  throw new Error('Content type is invalid: ' + useType)
@@ -535,7 +535,10 @@ export class CapiResponse {
535
535
  #createCAPIImage(image: MainImage | undefined) {
536
536
  if (image?.type === 'http://www.ft.com/ontology/content/Image') {
537
537
  return new CAPIImage(image, this.context)
538
- } else if (image?.type === 'http://www.ft.com/ontology/content/ImageSet') {
538
+ } else if (
539
+ image?.type === 'http://www.ft.com/ontology/content/ImageSet' &&
540
+ image.members[0]
541
+ ) {
539
542
  return new CAPIImage(image.members[0], this.context)
540
543
  }
541
544
 
@@ -753,7 +756,7 @@ export class CapiResponse {
753
756
  )
754
757
 
755
758
  if (pinnedPostIndex !== -1) {
756
- return liveBlogPosts.splice(pinnedPostIndex, 1)[0]
759
+ return liveBlogPosts.splice(pinnedPostIndex, 1)[0] ?? null
757
760
  }
758
761
  }
759
762
  return null
package/src/model/Clip.ts CHANGED
@@ -79,7 +79,7 @@ export class Clip implements ClipVideo {
79
79
  'full-grid': 1200,
80
80
  }
81
81
 
82
- const url = this.clip.poster?.members[0].binaryUrl
82
+ const url = this.clip.poster?.members[0]?.binaryUrl
83
83
  return url
84
84
  ? imageServiceUrl({
85
85
  url,
@@ -68,10 +68,10 @@ describe('Image', () => {
68
68
 
69
69
  it('includes sourceSet for all the possible resolutions we can display the image at, given the requested width and the original source width', () => {
70
70
  expect(sourceSet.length).toEqual(5)
71
- expect(sourceSet[0].dpr).toEqual(1)
72
- expect(sourceSet[0].url).toMatch(/dpr=1/)
73
- expect(sourceSet[4].dpr).toEqual(5)
74
- expect(sourceSet[4].url).toMatch(/dpr=5/)
71
+ expect(sourceSet[0]?.dpr).toEqual(1)
72
+ expect(sourceSet[0]?.url).toMatch(/dpr=1/)
73
+ expect(sourceSet[4]?.dpr).toEqual(5)
74
+ expect(sourceSet[4]?.url).toMatch(/dpr=5/)
75
75
  })
76
76
  })
77
77
 
@@ -109,7 +109,7 @@ describe('Image', () => {
109
109
  const model = new CAPIImage(mockImage, context)
110
110
  const sourceSet = await model.sourceSet({ width: MAX_IMAGE_WIDTH })
111
111
  expect(sourceSet.length).toEqual(1)
112
- expect(sourceSet[0].width).toEqual(MAX_IMAGE_WIDTH)
112
+ expect(sourceSet[0]?.width).toEqual(MAX_IMAGE_WIDTH)
113
113
  })
114
114
  })
115
115
 
@@ -119,8 +119,8 @@ describe('Image', () => {
119
119
  const model = new CAPIImage(mockImage, context)
120
120
  const sourceSet = await model.sourceSet({ width: 1000, maxDpr: 2 })
121
121
  expect(sourceSet.length).toEqual(2)
122
- expect(sourceSet[0].dpr).toEqual(1)
123
- expect(sourceSet[1].dpr).toEqual(2)
122
+ expect(sourceSet[0]?.dpr).toEqual(1)
123
+ expect(sourceSet[1]?.dpr).toEqual(2)
124
124
  })
125
125
  })
126
126
 
@@ -150,13 +150,13 @@ describe('Image', () => {
150
150
  })
151
151
  it('passes the requested width to the image service', async () => {
152
152
  expect(sourceSet.length).toEqual(1)
153
- expect(sourceSet[0].width).toEqual(3000)
154
- expect(sourceSet[0].url).toMatch(/width=3000/)
153
+ expect(sourceSet[0]?.width).toEqual(3000)
154
+ expect(sourceSet[0]?.url).toMatch(/width=3000/)
155
155
  })
156
156
  it('falls back to a single DPR source', async () => {
157
157
  expect(sourceSet.length).toEqual(1)
158
- expect(sourceSet[0].dpr).toEqual(1)
159
- expect(sourceSet[0].url).toMatch(/dpr=1/)
158
+ expect(sourceSet[0]?.dpr).toEqual(1)
159
+ expect(sourceSet[0]?.url).toMatch(/dpr=1/)
160
160
  })
161
161
  })
162
162
  })
@@ -59,8 +59,8 @@ describe('RichText resolver', () => {
59
59
  const model = new RichText('bodyXML', bodyXML)
60
60
  const result = await model.structured()
61
61
 
62
- expect(result.references[0].reference.type).toEqual('main-image')
63
- expect(result.references[1].reference.type).toEqual('image-set')
62
+ expect(result.references[0]?.reference.type).toEqual('main-image')
63
+ expect(result.references[1]?.reference.type).toEqual('image-set')
64
64
  })
65
65
 
66
66
  it('should not match the first imageset inside other components', async () => {
@@ -76,8 +76,8 @@ describe('RichText resolver', () => {
76
76
  const model = new RichText('bodyXML', bodyXML)
77
77
  const result = await model.structured()
78
78
 
79
- expect(result.references[0].reference.type).toEqual('main-image')
80
- expect(result.references[1].reference.type).toEqual('image-set')
79
+ expect(result.references[0]?.reference.type).toEqual('main-image')
80
+ expect(result.references[1]?.reference.type).toEqual('image-set')
81
81
  })
82
82
  })
83
83
  })
@@ -275,7 +275,7 @@ export class Topper {
275
275
  const isOpinionOrColumn =
276
276
  this.type() === 'OpinionTopper' || this.capiResponse.isColumn()
277
277
 
278
- return isOpinionOrColumn && authors.length ? authors[0] : null
278
+ return isOpinionOrColumn && authors[0] ? authors[0] : null
279
279
  }
280
280
 
281
281
  brandConcept() {
@@ -319,7 +319,7 @@ export class Topper {
319
319
  if (this.capiResponse.isOpinion()) {
320
320
  const authors = this.capiResponse.authors()
321
321
 
322
- if (authors && authors.length >= 1) {
322
+ if (authors && authors[0]) {
323
323
  return authors[0].headshot(args)
324
324
  }
325
325
  }
@@ -45,11 +45,13 @@ export default function bodyXMLToTree(
45
45
 
46
46
  if (matchedSelector) {
47
47
  const contentTreeTransform = tagMappings[matchedSelector]
48
- return contentTreeTransform(
49
- $(node),
50
- () => flattenAndTraverseChildren(node.children),
51
- context
52
- )
48
+ if (contentTreeTransform) {
49
+ return contentTreeTransform(
50
+ $(node),
51
+ () => flattenAndTraverseChildren(node.children),
52
+ context
53
+ )
54
+ }
53
55
  }
54
56
 
55
57
  return flattenAndTraverseChildren(node.children)
@@ -58,7 +60,12 @@ export default function bodyXMLToTree(
58
60
  return []
59
61
  }
60
62
 
61
- const body = traverse($('body')[0])
63
+ const cheerioBody = $('body')
64
+ if (!cheerioBody[0]) {
65
+ throw new Error('No body tag found in bodyXML')
66
+ }
67
+
68
+ const body = traverse(cheerioBody[0])
62
69
 
63
70
  logErrors(context)
64
71
 
@@ -40,7 +40,7 @@ describe('tagMappings test', () => {
40
40
  const selector =
41
41
  'ft-content[type="http://www.ft.com/ontology/content/clip"]'
42
42
  const $el = load(bodyXML)(selector)
43
- const mapping = tagMappings[selector]($el, () => [])
43
+ const mapping = tagMappings[selector]!($el, () => [])
44
44
 
45
45
  expectNodeType<OldClip>(mapping, 'clip')
46
46
  expect(mapping.url).toBe(
@@ -265,7 +265,7 @@ const commonTagMappings: TagMappings = {
265
265
  ): ContentTree.Layout['children'] => {
266
266
  const [firstChild, ...otherChildren] = children
267
267
 
268
- if (firstChild.type === 'heading') {
268
+ if (firstChild && firstChild.type === 'heading') {
269
269
  return [
270
270
  firstChild,
271
271
  ...everyChildIsType<ContentTree.LayoutSlot>(
@@ -317,9 +317,9 @@ const commonTagMappings: TagMappings = {
317
317
  table: ($el, traverse, context): Table | ContentTree.Layout => {
318
318
  const layoutSmallscreen = $el.attr('data-table-layout-smallscreen')
319
319
  const responsiveStyle: Table['responsiveStyle'] =
320
- layoutSmallscreen && layoutSmallscreen in tableResponsiveStyleMap
320
+ (layoutSmallscreen
321
321
  ? tableResponsiveStyleMap[layoutSmallscreen]
322
- : 'overflow'
322
+ : undefined) ?? 'overflow'
323
323
 
324
324
  const children = traverse()
325
325
  const bodies = children.filter(
@@ -342,14 +342,14 @@ const commonTagMappings: TagMappings = {
342
342
  // HACK:KB:20230523 transform single-cell tables into infoboxes, for legacy articles
343
343
  if (
344
344
  bodies.length === 1 &&
345
- bodies[0].children.length === 1 &&
346
- bodies[0].children[0].children.length === 1
345
+ bodies[0]?.children.length === 1 &&
346
+ bodies[0]?.children[0]?.children.length === 1
347
347
  ) {
348
348
  const slot: ContentTree.LayoutSlot = {
349
349
  type: 'layout-slot',
350
350
  children: childrenOfTypes(
351
351
  ['paragraph', 'heading'],
352
- bodies[0].children[0].children[0].children,
352
+ bodies[0]?.children[0]?.children[0]?.children ?? [],
353
353
  'layout-slot',
354
354
  context
355
355
  ),
@@ -548,7 +548,7 @@ const commonTagMappings: TagMappings = {
548
548
  children: [
549
549
  ...everyChildIsType<ContentTree.ScrollyImage>(
550
550
  'scrolly-image',
551
- [firstChild],
551
+ firstChild ? [firstChild] : [],
552
552
  'scrolly-section',
553
553
  context
554
554
  ),