• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

scriptype / writ-cms / 20182077340

12 Dec 2025 10:49PM UTC coverage: 32.767% (-0.9%) from 33.638%
20182077340

push

github

scriptype
Automate childNode matching and parsing in ContentModelEntryNode

Introduce a getSubtreeConfig method, in which all necessities of
matching childNodes and building subtree out of them are defined.

For each key in subtree, a definition like this is added to the config
array:

{
  matcher: matcha;
  key?: string;
  settings?: object;
  model?: ContentModelNode constructor;
  singular?: boolean (false);
  sideEffect?: function;
}

Then parseSubtree template method is actually implemented in the
ContentModelEntryNode. It loops over childNodes and subtree configs.

For everything to look and feel smooth some additional changes are made:

- ContentModel does not pass a contentType property to collection any
more. Instead, collection itself computes it based on the
settings.contentTypes in its getSchema method
- Compiler passes the { children: fileSystemTree } to the contentModel
- static draftCheck is now a class method of ContentModelEntryNode
- A getChildContext template method is added for models to be able to
customize the context they pass to their childNodes. E.g. ContentModel,
AssetsDirectory, PagesDirectory

412 of 3375 branches covered (12.21%)

Branch coverage included in aggregate %.

84 of 92 new or added lines in 9 files covered. (91.3%)

1 existing line in 1 file now uncovered.

1382 of 2100 relevant lines covered (65.81%)

1253.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.06
/src/lib/ContentModelEntryNode.js
1
const { join } = require('path')
9✔
2
const makeSlug = require('slug')
9✔
3
const { makePermalink } = require('./contentModelHelpers')
9✔
4
const { parseTextEntry } = require('./parseTextEntry')
9✔
5
const ContentModelNode = require('./ContentModelNode')
9✔
6

7
class ContentModelEntryNode extends ContentModelNode {
8
  constructor(fsNode, context, settings = {}) {
×
9
    super(fsNode, context, settings)
2,340✔
10

11
    this.indexFile = this.getIndexFile()
2,340✔
12

13
    const isFlatData = !fsNode.stats?.birthtime
2,340✔
14
    const entryProperties = parseTextEntry(
2,340✔
15
      this.fsNode,
16
      this.indexFile || this.fsNode,
2,547✔
17
      isFlatData
18
    )
19

20
    Object.assign(this, entryProperties)
2,340✔
21

22
    this.slug = this.getSlug()
2,340✔
23
    this.permalink = this.getPermalink()
2,340✔
24
    this.outputPath = this.getOutputPath()
2,340✔
25
  }
26

27
  getIndexFile() {
28
    return this.fsNode
72✔
29
  }
30

31
  getSlug() {
32
    return this.slug
2,160✔
33
  }
34

35
  getPermalink() {
36
    return makePermalink(
1,485✔
37
      this.context.peek().permalink,
38
      this.slug
39
    ) + (this.hasIndex ? '' : '.html')
1,485✔
40
  }
41

42
  getOutputPath() {
43
    return join(
2,097✔
44
      this.context.peek().outputPath,
45
      this.slug
46
    )
47
  }
48

49
  /*
50
   * subtreeConfig = {
51
   *   matcher: matcha;
52
   *   key?: string;
53
   *   settings?: object;
54
   *   model?: ContentModelNode constructor;
55
   *   singular?: boolean (false);
56
   *   sideEffect?: function;
57
   * }[]
58
   * */
59
  getSubtreeConfig() {
NEW
60
    return []
×
61
  }
62

63
  getChildContext() {
64
    return undefined
1,881✔
65
  }
66

67
  parseSubtree(tree) {
68
    const childContext = this.getChildContext() || this.context.push({
2,160✔
69
      title: this.title,
70
      slug: this.slug,
71
      permalink: this.permalink,
72
      outputPath: this.outputPath,
73
      key: this.contextKey
74
    })
75
    const childNodes = (this.fsNode.children || []).filter(n => n !== this.indexFile)
2,826✔
76
    childNodeLoop: for (const childNode of childNodes) {
2,160✔
77
      configLoop: for (const config of this.subtreeConfig) {
2,106✔
78
        if (!config.matcher(childNode)) {
4,284✔
79
          // config does not match the node, move to next config
80
          continue configLoop
2,178✔
81
        }
82
        if (config.model) {
2,106✔
83
          const childModel = new config.model(childNode, childContext, config.settings)
1,566✔
84
          if (!this.draftCheck(childModel)) {
1,566!
85
            // entry is draft, move on to next childNode
NEW
86
            continue childNodeLoop
×
87
          }
88
          if (config.singular) {
1,566✔
89
            tree[config.key] = childModel
108✔
90
          } else {
91
            tree[config.key].push(childModel)
1,458✔
92
          }
93
          if (typeof config.sideEffect === 'function') {
1,566✔
94
            config.sideEffect(tree, childModel)
792✔
95
          }
96
        } else if (typeof config.sideEffect === 'function') {
540!
97
          config.sideEffect(tree, childNode)
540✔
98
        }
99
        // we are done with this childNode, do not look at other configs
100
        continue childNodeLoop
2,106✔
101
      }
102
    }
103
    return tree
2,160✔
104
  }
105

106
  draftCheck(entry) {
107
    return this.settings.mode === 'start' || !entry.draft
2,106✔
108
  }
109

110
  afterEffects(contentModel) {}
111

112
  render(renderer) {}
113
}
114

115
module.exports = ContentModelEntryNode
9✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc