Skip to content

Commit

Permalink
Refactor + add more tests (Part 1) (#847)
Browse files Browse the repository at this point in the history
* Refactor mdToHtml out

* Refactor routing + move it to server instead of core

* Refactor & Add more tests for server utils

* Refactor isSeparateCss function from server & generate

* Refactor insertTableOfContents from server & generate + add tests

* undo small nits
  • Loading branch information
endiliey authored and yangshun committed Jul 13, 2018
1 parent a7a214f commit defcbcc
Show file tree
Hide file tree
Showing 14 changed files with 322 additions and 235 deletions.
28 changes: 28 additions & 0 deletions lib/core/__tests__/__fixtures__/insertTOC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
id: pokemon-commands
title: Pokemon Commands
---

## Commands

<AUTOGENERATED_TABLE_OF_CONTENTS>

---

## Reference

### `pokemon-run`

Alias: `run`.

### `pokemon-fight`

Alias: `fight`

### `pokemon-bag`

Alias: `bag`

### `pokemon-rename`

Alias: `rename`
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`with custom heading levels 1`] = `
exports[`getTOC with custom heading levels 1`] = `
Array [
Object {
"children": Array [
Expand Down Expand Up @@ -105,7 +105,7 @@ Array [
]
`;

exports[`with defaults 1`] = `
exports[`getTOC with defaults 1`] = `
Array [
Object {
"children": Array [
Expand Down Expand Up @@ -185,3 +185,57 @@ Array [
},
]
`;

exports[`insertTOC AUTOGENERATED_TABLE_OF_CONTENTS does not exist 1`] = `
"## foo
### foo
### foo 1
## foo 1
## foo 2
### foo
#### 4th level headings
All 4th level headings should not be shown by default
## bar
### bar
#### bar
4th level heading should be ignored by default, but is should be always taken
into account, when generating slugs
### \`bar\`
#### \`bar\`
## bar
### bar
#### bar
## bar
"
`;

exports[`insertTOC AUTOGENERATED_TABLE_OF_CONTENTS exists 1`] = `
"
## Commands
- [\`pokemon-run\`](#pokemon-run)
- [\`pokemon-fight\`](#pokemon-fight)
- [\`pokemon-bag\`](#pokemon-bag)
- [\`pokemon-rename\`](#pokemon-rename)
---
## Reference
### \`pokemon-run\`
Alias: \`run\`.
### \`pokemon-fight\`
Alias: \`fight\`
### \`pokemon-bag\`
Alias: \`bag\`
### \`pokemon-rename\`
Alias: \`rename\`"
`;
33 changes: 0 additions & 33 deletions lib/core/__tests__/getTOC.test.js

This file was deleted.

60 changes: 60 additions & 0 deletions lib/core/__tests__/toc.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const path = require('path');
const readFileSync = require('fs').readFileSync;
const {getTOC, insertTOC} = require('../toc');
const {extractMetadata} = require('../../server/metadataUtils');

const getTOCmd = readFileSync(
path.join(__dirname, '__fixtures__', 'getTOC.md'),
'utf8'
);

const insertTOCmd = readFileSync(
path.join(__dirname, '__fixtures__', 'insertTOC.md'),
'utf8'
);

describe('getTOC', () => {
test('with defaults', () => {
const headings = getTOC(getTOCmd);
const headingsJson = JSON.stringify(headings);

expect(headings).toMatchSnapshot();
expect(headingsJson).toContain('bar-8'); // maximum unique bar index is 8
expect(headingsJson).not.toContain('4th level headings');
});

test('with custom heading levels', () => {
const headings = getTOC(getTOCmd, 'h2', ['h3', 'h4']);
const headingsJson = JSON.stringify(headings);

expect(headings).toMatchSnapshot();
expect(headingsJson).toContain('bar-8'); // maximum unique bar index is 8
expect(headingsJson).toContain('4th level headings');
});
});

describe('insertTOC', () => {
test('null or undefined content', () => {
expect(insertTOC(null)).toBeNull();
expect(insertTOC(undefined)).toBeUndefined();
});

test('AUTOGENERATED_TABLE_OF_CONTENTS does not exist', () => {
const rawContent = extractMetadata(getTOCmd).rawContent;
expect(insertTOC(rawContent)).toMatchSnapshot();
expect(insertTOC(rawContent)).toEqual(rawContent);
});

test('AUTOGENERATED_TABLE_OF_CONTENTS exists', () => {
const rawContent = extractMetadata(insertTOCmd).rawContent;
expect(insertTOC(rawContent)).toMatchSnapshot();
expect(insertTOC(rawContent)).not.toEqual(rawContent);
});
});
2 changes: 1 addition & 1 deletion lib/core/nav/OnPageNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
const React = require('react');

const siteConfig = require(`${process.cwd()}/siteConfig.js`);
const getTOC = require('../getTOC');
const {getTOC} = require('../toc');

const Link = ({hashLink, content}) => (
<a
Expand Down
31 changes: 23 additions & 8 deletions lib/core/getTOC.js → lib/core/toc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const Remarkable = require('remarkable');
const mdToc = require('markdown-toc');
const toSlug = require('./toSlug');

const tagToLevel = tag => Number(tag.slice(1));
const TABLE_OF_CONTENTS_TOKEN = '<AUTOGENERATED_TABLE_OF_CONTENTS>';

/**
* Returns a table of content from the headings
Expand All @@ -18,16 +18,15 @@ const tagToLevel = tag => Number(tag.slice(1));
* Array of heading objects with `hashLink`, `content` and `children` fields
*
*/
module.exports = (content, headingTags = 'h2', subHeadingTags = 'h3') => {
function getTOC(content, headingTags = 'h2', subHeadingTags = 'h3') {
const tagToLevel = tag => Number(tag.slice(1));
const headingLevels = [].concat(headingTags).map(tagToLevel);
const subHeadingLevels = subHeadingTags
? [].concat(subHeadingTags).map(tagToLevel)
: [];
const allowedHeadingLevels = headingLevels.concat(subHeadingLevels);

const md = new Remarkable();
const headings = mdToc(content).json;

const toc = [];
const context = {};
let current;
Expand All @@ -36,26 +35,42 @@ module.exports = (content, headingTags = 'h2', subHeadingTags = 'h3') => {
// we need always generate slugs to ensure, that we will have consistent
// slug indexes for headings with the same names
const hashLink = toSlug(heading.content, context);

if (!allowedHeadingLevels.includes(heading.lvl)) {
return;
}

const rawContent = mdToc.titleize(heading.content);
const entry = {
hashLink,
rawContent,
content: md.renderInline(rawContent),
children: [],
};

if (headingLevels.includes(heading.lvl)) {
toc.push(entry);
current = entry;
} else if (current) {
current.children.push(entry);
}
});

return toc;
}

// takes the content of a doc article and returns the content with a table of
// contents inserted
function insertTOC(rawContent) {
if (!rawContent || rawContent.indexOf(TABLE_OF_CONTENTS_TOKEN) === -1) {
return rawContent;
}
const filterRe = /^`[^`]*`/;
const headers = getTOC(rawContent, 'h3', null);
const tableOfContents = headers
.filter(header => filterRe.test(header.rawContent))
.map(header => ` - [${header.rawContent}](#${header.hashLink})`)
.join('\n');
return rawContent.replace(TABLE_OF_CONTENTS_TOKEN, tableOfContents);
}

module.exports = {
getTOC,
insertTOC,
};
1 change: 0 additions & 1 deletion lib/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ function getPath(path, cleanUrl = false) {
? path.replace(/\/index.html$/, '')
: removeExtension(path);
}

return path;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/

const {
blogRouting,
docsRouting,
dotRouting,
feedRouting,
noExtRouting,
pageRouting,
sitemapRouting,
} = require('../routing');
const routing = require('../routing');

describe('Blog routing', () => {
const blogRegex = blogRouting('/');
const blogRegex2 = blogRouting('/react/');
const blogRegex = routing.blog('/');
const blogRegex2 = routing.blog('/react/');

test('valid blog', () => {
expect('/blog/test.html').toMatch(blogRegex);
Expand All @@ -43,8 +35,8 @@ describe('Blog routing', () => {
});

describe('Docs routing', () => {
const docsRegex = docsRouting('/');
const docsRegex2 = docsRouting('/reason/');
const docsRegex = routing.docs('/');
const docsRegex2 = routing.docs('/reason/');

test('valid docs', () => {
expect('/docs/en/test.html').toMatch(docsRegex);
Expand All @@ -70,7 +62,7 @@ describe('Docs routing', () => {
});

describe('Dot routing', () => {
const dotRegex = dotRouting();
const dotRegex = routing.dotfiles();

test('valid url with dot after last slash', () => {
expect('/docs/en/test.23').toMatch(dotRegex);
Expand All @@ -96,8 +88,8 @@ describe('Dot routing', () => {
});

describe('Feed routing', () => {
const feedRegex = feedRouting('/');
const feedRegex2 = feedRouting('/reason/');
const feedRegex = routing.feed('/');
const feedRegex2 = routing.feed('/reason/');

test('valid feed url', () => {
expect('/blog/atom.xml').toMatch(feedRegex);
Expand Down Expand Up @@ -126,7 +118,7 @@ describe('Feed routing', () => {
});

describe('Extension-less url routing', () => {
const noExtRegex = noExtRouting();
const noExtRegex = routing.noExtension();

test('valid no extension url', () => {
expect('/test').toMatch(noExtRegex);
Expand All @@ -146,8 +138,8 @@ describe('Extension-less url routing', () => {
});

describe('Page routing', () => {
const pageRegex = pageRouting('/');
const pageRegex2 = pageRouting('/reason/');
const pageRegex = routing.page('/');
const pageRegex2 = routing.page('/reason/');

test('valid page url', () => {
expect('/index.html').toMatch(pageRegex);
Expand All @@ -173,8 +165,8 @@ describe('Page routing', () => {
});

describe('Sitemap routing', () => {
const sitemapRegex = sitemapRouting('/');
const sitemapRegex2 = sitemapRouting('/reason/');
const sitemapRegex = routing.sitemap('/');
const sitemapRegex2 = routing.sitemap('/reason/');

test('valid sitemap url', () => {
expect('/sitemap.xml').toMatch(sitemapRegex);
Expand Down
Loading

0 comments on commit defcbcc

Please sign in to comment.