From bad3d07a24a951cda699954401e605058becd8ee Mon Sep 17 00:00:00 2001 From: HutchGrant Date: Mon, 22 Apr 2019 17:08:37 -0400 Subject: [PATCH 01/18] fix: adding meta web component --- test/fixtures/mock-app/src/components/meta.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 test/fixtures/mock-app/src/components/meta.js diff --git a/test/fixtures/mock-app/src/components/meta.js b/test/fixtures/mock-app/src/components/meta.js new file mode 100644 index 000000000..3105c2d1b --- /dev/null +++ b/test/fixtures/mock-app/src/components/meta.js @@ -0,0 +1,33 @@ +import { html, LitElement } from 'lit-element'; + +class meta extends LitElement { + + static get properties() { + return { + attributes: { + type: Object + } + }; + } + + firstUpdated() { + let header = document.head; + let meta; + + this.attributes.map((attr) => { + meta = document.createElement('meta'); + meta.setAttribute(attr[0], attr[1]); + header.appendChild(meta); + }); + } + + render() { + return html` +
+ +
+ `; + } +} + +customElements.define('eve-meta', meta); \ No newline at end of file From ecfb61eee3667188ffb0c327b2ab036108857c1a Mon Sep 17 00:00:00 2001 From: "Grant Hutchinson (hutchgrant)" Date: Thu, 2 May 2019 05:44:32 -0400 Subject: [PATCH 02/18] fix: adding meta component to default page template and scaffold --- package.json | 2 +- packages/cli/config/webpack.config.common.js | 12 +++-- packages/cli/lib/config.js | 18 +++++--- packages/cli/lib/graph.js | 4 +- packages/cli/lib/init.js | 5 +- packages/cli/lib/scaffold.js | 46 ++++++++++++++++++- packages/cli/templates/app-template.js | 2 + .../templates/components/header/header.css | 19 ++++++++ .../cli/templates/components/header/header.js | 19 ++++++++ packages/cli/templates/components/meta.js | 40 ++++++++++++++++ packages/cli/templates/index.md | 1 + packages/cli/templates/page-template.js | 3 ++ test/fixtures/mock-app/src/components/meta.js | 40 ++++++++++++++++ 13 files changed, 196 insertions(+), 15 deletions(-) create mode 100644 packages/cli/templates/components/header/header.css create mode 100644 packages/cli/templates/components/header/header.js create mode 100644 packages/cli/templates/components/meta.js create mode 100644 test/fixtures/mock-app/src/components/meta.js diff --git a/package.json b/package.json index 14b00ff38..448f6ea3e 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "build": "node ./packages/cli/index.js build", "serve": "yarn clean && yarn build && cd ./public && ws", "develop": "yarn clean && node ./packages/cli/index.js develop", - "test": "yarn clean && mocha --timeout 15000" + "test": "yarn clean && mocha --timeout 30000" }, "dependencies": { "@babel/core": "^7.4.0", diff --git a/packages/cli/config/webpack.config.common.js b/packages/cli/config/webpack.config.common.js index 44e561a51..7210e53e7 100644 --- a/packages/cli/config/webpack.config.common.js +++ b/packages/cli/config/webpack.config.common.js @@ -7,13 +7,18 @@ const isDirectory = source => fs.lstatSync(source).isDirectory(); const getUserWorkspaceDirectories = (source) => { return fs.readdirSync(source).map(name => path.join(source, name)).filter(isDirectory); }; -const mapUserWorkspaceDirectory = (userPath) => { +const mapUserWorkspaceDirectory = (userPath, userWorkspace) => { const directory = userPath.split('/')[userPath.split('/').length - 1]; return new webpack.NormalModuleReplacementPlugin( new RegExp(`${directory}`), (resource) => { - resource.request = resource.request.replace(new RegExp(`\.\.\/${directory}`), userPath); + let regex = new RegExp(`\.\.\/${directory}`); + + if(userWorkspace === path.join(__dirname, '..', 'templates/')) { + regex = new RegExp(`\.\/${directory}`); + } + resource.request = resource.request.replace(regex, userPath); // remove any additional nests, after replacement with absolute path of user workspace + directory const additionalNestedPathIndex = resource.request.lastIndexOf('..'); @@ -26,9 +31,10 @@ const mapUserWorkspaceDirectory = (userPath) => { }; module.exports = (config, context) => { + // dynamically map all the user's workspace directories for resolution by webpack // this essentially helps us keep watch over changes from the user, and greenwood's build pipeline - const mappedUserDirectoriesForWebpack = getUserWorkspaceDirectories(context.userWorkspace).map(mapUserWorkspaceDirectory); + const mappedUserDirectoriesForWebpack = getUserWorkspaceDirectories(context.userWorkspace).map(userPath => mapUserWorkspaceDirectory(userPath, context.userWorkspace)); return { diff --git a/packages/cli/lib/config.js b/packages/cli/lib/config.js index d4014206f..1498a103d 100644 --- a/packages/cli/lib/config.js +++ b/packages/cli/lib/config.js @@ -9,14 +9,10 @@ let config = { host: 'http://localhost' }, publicPath: '/', + title: '', // TODO add global meta data see issue #5 // https://github.com/ProjectEvergreen/greenwood/issues/5 - meta: { - title: '', - description: '', - author: '', - domain: '' - } + meta: [] }; module.exports = readAndMergeConfig = async() => { @@ -27,7 +23,7 @@ module.exports = readAndMergeConfig = async() => { if (fs.existsSync(path.join(process.cwd(), 'greenwood.config.js'))) { const userCfgFile = require(path.join(process.cwd(), 'greenwood.config.js')); - const { workspace, devServer, publicPath } = userCfgFile; + const { workspace, devServer, publicPath, title, meta } = userCfgFile; if (workspace) { if (typeof workspace !== 'string') { @@ -78,6 +74,14 @@ module.exports = readAndMergeConfig = async() => { console.log('custom port provided => ', customConfig.devServer.port); } } + + } + if(meta && meta.length > 0) { + customConfig.meta = meta; + } + + if(title) { + customConfig.title = title; } config = { ...config, ...customConfig }; diff --git a/packages/cli/lib/graph.js b/packages/cli/lib/graph.js index d0e1c0b8c..a9afa3a36 100644 --- a/packages/cli/lib/graph.js +++ b/packages/cli/lib/graph.js @@ -26,7 +26,7 @@ const createGraphFromPages = async (pagesDir) => { if (isMdFile && !stats.isDirectory()) { const fileContents = await readFile(filePath, 'utf8'); const { attributes } = fm(fileContents); - let { label, template } = attributes; + let { label, template, title } = attributes; let mdFile = ''; // if template not set, use default @@ -78,7 +78,7 @@ const createGraphFromPages = async (pagesDir) => { * elementLabel: the element name for the generated md page e.g. */ - pages.push({ mdFile, label, route, template, filePath, fileName, relativeExpectedPath }); + pages.push({ mdFile, label, title, route, template, filePath, fileName, relativeExpectedPath }); } if (stats.isDirectory()) { await walkDirectory(filePath); diff --git a/packages/cli/lib/init.js b/packages/cli/lib/init.js index d6fa29040..a88d9def8 100644 --- a/packages/cli/lib/init.js +++ b/packages/cli/lib/init.js @@ -3,6 +3,7 @@ const path = require('path'); const defaultTemplatesDir = path.join(__dirname, '../templates/'); const scratchDir = path.join(process.cwd(), './.greenwood/'); const publicDir = path.join(process.cwd(), './public'); +const metaComponent = './components/meta'; // intentionally not absolute path until webpack config can accomodate module.exports = initContexts = async({ config }) => { @@ -22,6 +23,7 @@ module.exports = initContexts = async({ config }) => { const userHasWorkspaceAppTemplate = fs.existsSync(userAppTemplate); let context = { + defaultTemplatesDir, scratchDir, publicDir, pagesDir: userHasWorkspacePages ? userPagesDir : defaultTemplatesDir, @@ -34,7 +36,8 @@ module.exports = initContexts = async({ config }) => { ? userAppTemplate : path.join(defaultTemplatesDir, 'app-template.js'), indexPageTemplate: 'index.html', - notFoundPageTemplate: '404.html' + notFoundPageTemplate: '404.html', + metaComponent }; if (!fs.existsSync(scratchDir)) { diff --git a/packages/cli/lib/scaffold.js b/packages/cli/lib/scaffold.js index 848d44715..4b762ceeb 100644 --- a/packages/cli/lib/scaffold.js +++ b/packages/cli/lib/scaffold.js @@ -22,6 +22,50 @@ const writePageComponentsFromTemplate = async (compilation) => { } }); }; + const loadPageMeta = async (file, result, { context, config }) => { + return new Promise((resolve, reject) => { + try{ + const metadata = { + title: '', + meta: [] + }; + let title = '', metaComponent = ''; + + if(config.meta && config.meta.length > 0) { + metadata.meta = config.meta; + } + + if(config.title) { + title = config.title; + } + + // override title with dynamic title per page if available + if(file.title) { + title = file.title; + } + metadata.title = title; + metadata.meta["og:title"] = title; + + // Temporary workaround to webpack config's import path adjustment issue + // when using importing default template components ./ vs ../ for userWorkspace + + // if we're using default page template + const isDefaultTemplate = context.userWorkspace === path.join(context.defaultTemplatesDir); + + metaComponent = isDefaultTemplate + ? './components/meta.js' + : '../components/meta.js'; + + result = result.replace(/METAIMPORT/, `import '${metaComponent}'`); + result = result.replace(/METADATA/, `const metadata = ${JSON.stringify(metadata)}`); + result = result.replace(/METAELEMENT/, ``); + + resolve(result); + } catch(err) { + reject(err) + } + }); + }; return Promise.all(compilation.graph.map(file => { const context = compilation.context; @@ -29,7 +73,7 @@ const writePageComponentsFromTemplate = async (compilation) => { return new Promise(async(resolve, reject) => { try { let result = await createPageComponent(file, context); - + result = await loadPageMeta(file, result, compilation) let relPageDir = file.filePath.substring(context.pagesDir.length, file.filePath.length); const pathLastBackslash = relPageDir.lastIndexOf('/'); diff --git a/packages/cli/templates/app-template.js b/packages/cli/templates/app-template.js index 275d6843d..e9103d488 100644 --- a/packages/cli/templates/app-template.js +++ b/packages/cli/templates/app-template.js @@ -13,6 +13,7 @@ const store = createStore( compose(lazyReducerEnhancer(combineReducers), applyMiddleware(thunk))); import '../index/index.js'; +import './components/header/header'; import './list'; connectRouter(store); @@ -20,6 +21,7 @@ connectRouter(store); class AppComponent extends LitElement { render() { return html` + MYROUTES

404 Not found

`; diff --git a/packages/cli/templates/components/header/header.css b/packages/cli/templates/components/header/header.css new file mode 100644 index 000000000..18fd20e12 --- /dev/null +++ b/packages/cli/templates/components/header/header.css @@ -0,0 +1,19 @@ +:host { + & .header { + grid-area: header; + background-color: #006400; + min-height: 30px; + padding-top: 10px; + + & h4 { + width: 90%; + margin: 0 auto; + padding: 0; + text-align: center; + } + + & a { + color: white; + } + } +} \ No newline at end of file diff --git a/packages/cli/templates/components/header/header.js b/packages/cli/templates/components/header/header.js new file mode 100644 index 000000000..5707f2ed7 --- /dev/null +++ b/packages/cli/templates/components/header/header.js @@ -0,0 +1,19 @@ +import { html, LitElement } from 'lit-element'; +import css from './header.css'; + +class HeaderComponent extends LitElement { + render() { + return html` + +
+

+ PROJECT EVERGREEN +

+
+ `; + } +} + +customElements.define('eve-header', HeaderComponent); \ No newline at end of file diff --git a/packages/cli/templates/components/meta.js b/packages/cli/templates/components/meta.js new file mode 100644 index 000000000..172bdf5cd --- /dev/null +++ b/packages/cli/templates/components/meta.js @@ -0,0 +1,40 @@ +import { html, LitElement } from 'lit-element'; + +class meta extends LitElement { + + static get properties() { + return { + attributes: { + type: Object + } + }; + } + + firstUpdated() { + let header = document.head; + let meta; + + this.attributes.meta.map(attr => { + meta = document.createElement('meta'); + meta.setAttribute(Object.keys(attr)[0], Object.values(attr)[0]); + meta.setAttribute(Object.keys(attr)[1], Object.values(attr)[1]); + header.appendChild(meta); + }); + let title = document.createElement('title'); + + title.innerText = this.attributes.title; + const oldTitle = document.head.querySelector('title'); + + header.replaceChild(title, oldTitle); + } + + render() { + return html` +
+ +
+ `; + } +} + +customElements.define('eve-meta', meta); \ No newline at end of file diff --git a/packages/cli/templates/index.md b/packages/cli/templates/index.md index 19300efd8..ba4389313 100644 --- a/packages/cli/templates/index.md +++ b/packages/cli/templates/index.md @@ -1,5 +1,6 @@ --- label: 'index' +title: 'Greenwood Index Page' --- ### Greenwood diff --git a/packages/cli/templates/page-template.js b/packages/cli/templates/page-template.js index 16ff0fe5a..3367e1c4d 100644 --- a/packages/cli/templates/page-template.js +++ b/packages/cli/templates/page-template.js @@ -1,9 +1,12 @@ import { html, LitElement } from 'lit-element'; MDIMPORT; +METAIMPORT; +METADATA class PageTemplate extends LitElement { render() { return html` + METAELEMENT
diff --git a/test/fixtures/mock-app/src/components/meta.js b/test/fixtures/mock-app/src/components/meta.js new file mode 100644 index 000000000..172bdf5cd --- /dev/null +++ b/test/fixtures/mock-app/src/components/meta.js @@ -0,0 +1,40 @@ +import { html, LitElement } from 'lit-element'; + +class meta extends LitElement { + + static get properties() { + return { + attributes: { + type: Object + } + }; + } + + firstUpdated() { + let header = document.head; + let meta; + + this.attributes.meta.map(attr => { + meta = document.createElement('meta'); + meta.setAttribute(Object.keys(attr)[0], Object.values(attr)[0]); + meta.setAttribute(Object.keys(attr)[1], Object.values(attr)[1]); + header.appendChild(meta); + }); + let title = document.createElement('title'); + + title.innerText = this.attributes.title; + const oldTitle = document.head.querySelector('title'); + + header.replaceChild(title, oldTitle); + } + + render() { + return html` +
+ +
+ `; + } +} + +customElements.define('eve-meta', meta); \ No newline at end of file From 188b18bce69a62d934d742194173928859540188 Mon Sep 17 00:00:00 2001 From: "Grant Hutchinson (hutchgrant)" Date: Thu, 2 May 2019 06:09:31 -0400 Subject: [PATCH 03/18] fix: resolving lint issues --- packages/cli/config/webpack.config.common.js | 2 +- packages/cli/lib/config.js | 4 +-- packages/cli/lib/init.js | 2 +- packages/cli/lib/scaffold.js | 27 ++++++++++---------- packages/cli/templates/page-template.js | 2 +- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/cli/config/webpack.config.common.js b/packages/cli/config/webpack.config.common.js index 7210e53e7..76fd29053 100644 --- a/packages/cli/config/webpack.config.common.js +++ b/packages/cli/config/webpack.config.common.js @@ -15,7 +15,7 @@ const mapUserWorkspaceDirectory = (userPath, userWorkspace) => { (resource) => { let regex = new RegExp(`\.\.\/${directory}`); - if(userWorkspace === path.join(__dirname, '..', 'templates/')) { + if (userWorkspace === path.join(__dirname, '..', 'templates/')) { regex = new RegExp(`\.\/${directory}`); } resource.request = resource.request.replace(regex, userPath); diff --git a/packages/cli/lib/config.js b/packages/cli/lib/config.js index 1498a103d..6aaa7fde0 100644 --- a/packages/cli/lib/config.js +++ b/packages/cli/lib/config.js @@ -76,11 +76,11 @@ module.exports = readAndMergeConfig = async() => { } } - if(meta && meta.length > 0) { + if (meta && meta.length > 0) { customConfig.meta = meta; } - if(title) { + if (title) { customConfig.title = title; } diff --git a/packages/cli/lib/init.js b/packages/cli/lib/init.js index a88d9def8..03f33672b 100644 --- a/packages/cli/lib/init.js +++ b/packages/cli/lib/init.js @@ -3,7 +3,7 @@ const path = require('path'); const defaultTemplatesDir = path.join(__dirname, '../templates/'); const scratchDir = path.join(process.cwd(), './.greenwood/'); const publicDir = path.join(process.cwd(), './public'); -const metaComponent = './components/meta'; // intentionally not absolute path until webpack config can accomodate +const metaComponent = './components/meta'; // intentionally not absolute path until webpack config can accomodate module.exports = initContexts = async({ config }) => { diff --git a/packages/cli/lib/scaffold.js b/packages/cli/lib/scaffold.js index 4b762ceeb..6f7e0a2fb 100644 --- a/packages/cli/lib/scaffold.js +++ b/packages/cli/lib/scaffold.js @@ -24,45 +24,45 @@ const writePageComponentsFromTemplate = async (compilation) => { }; const loadPageMeta = async (file, result, { context, config }) => { return new Promise((resolve, reject) => { - try{ + try { const metadata = { title: '', meta: [] }; let title = '', metaComponent = ''; - if(config.meta && config.meta.length > 0) { + if (config.meta && config.meta.length > 0) { metadata.meta = config.meta; } - if(config.title) { + if (config.title) { title = config.title; } // override title with dynamic title per page if available - if(file.title) { + if (file.title) { title = file.title; } metadata.title = title; - metadata.meta["og:title"] = title; + metadata.meta['og:title'] = title; // Temporary workaround to webpack config's import path adjustment issue // when using importing default template components ./ vs ../ for userWorkspace // if we're using default page template - const isDefaultTemplate = context.userWorkspace === path.join(context.defaultTemplatesDir); + const isDefaultTemplate = context.userWorkspace === path.join(context.defaultTemplatesDir); - metaComponent = isDefaultTemplate - ? './components/meta.js' - : '../components/meta.js'; + metaComponent = isDefaultTemplate + ? './components/meta.js' + : '../components/meta.js'; result = result.replace(/METAIMPORT/, `import '${metaComponent}'`); result = result.replace(/METADATA/, `const metadata = ${JSON.stringify(metadata)}`); - result = result.replace(/METAELEMENT/, ``); + result = result.replace(/METAELEMENT/, ''); resolve(result); - } catch(err) { - reject(err) + } catch (err) { + reject(err); } }); }; @@ -73,7 +73,8 @@ const writePageComponentsFromTemplate = async (compilation) => { return new Promise(async(resolve, reject) => { try { let result = await createPageComponent(file, context); - result = await loadPageMeta(file, result, compilation) + + result = await loadPageMeta(file, result, compilation); let relPageDir = file.filePath.substring(context.pagesDir.length, file.filePath.length); const pathLastBackslash = relPageDir.lastIndexOf('/'); diff --git a/packages/cli/templates/page-template.js b/packages/cli/templates/page-template.js index 3367e1c4d..4355f1987 100644 --- a/packages/cli/templates/page-template.js +++ b/packages/cli/templates/page-template.js @@ -1,7 +1,7 @@ import { html, LitElement } from 'lit-element'; MDIMPORT; METAIMPORT; -METADATA +METADATA; class PageTemplate extends LitElement { render() { From 883c295a1a9d9a166abcd701bc82df699a01fca4 Mon Sep 17 00:00:00 2001 From: HutchGrant Date: Wed, 8 May 2019 20:09:02 -0400 Subject: [PATCH 04/18] fix: merge config meta with graph, updated meta component to prevent rehydration, update webpack ModuleReplacement exception --- packages/cli/config/webpack.config.common.js | 9 ++-- packages/cli/config/webpack.config.develop.js | 2 +- packages/cli/config/webpack.config.prod.js | 2 +- packages/cli/lib/compile.js | 2 +- packages/cli/lib/config.js | 15 ++++-- packages/cli/lib/graph.js | 25 +++++++--- packages/cli/lib/init.js | 4 +- packages/cli/lib/scaffold.js | 27 ++++++++++ packages/cli/templates/components/meta.js | 50 +++++++++++++++++++ packages/cli/templates/page-template.js | 3 ++ test/fixtures/mock-app/src/components/meta.js | 33 ------------ 11 files changed, 121 insertions(+), 51 deletions(-) create mode 100644 packages/cli/templates/components/meta.js delete mode 100644 test/fixtures/mock-app/src/components/meta.js diff --git a/packages/cli/config/webpack.config.common.js b/packages/cli/config/webpack.config.common.js index 44e561a51..8ffc96f87 100644 --- a/packages/cli/config/webpack.config.common.js +++ b/packages/cli/config/webpack.config.common.js @@ -13,11 +13,14 @@ const mapUserWorkspaceDirectory = (userPath) => { return new webpack.NormalModuleReplacementPlugin( new RegExp(`${directory}`), (resource) => { - resource.request = resource.request.replace(new RegExp(`\.\.\/${directory}`), userPath); + // workaround to ignore cli/templates default imports when rewriting + if (!new RegExp('\/cli\/templates').test(resource.request)) { + resource.request = resource.request.replace(new RegExp(`\.\.\/${directory}`), userPath); + } // remove any additional nests, after replacement with absolute path of user workspace + directory const additionalNestedPathIndex = resource.request.lastIndexOf('..'); - + if (additionalNestedPathIndex > -1) { resource.request = resource.request.substring(additionalNestedPathIndex + 2, resource.request.length); } @@ -25,7 +28,7 @@ const mapUserWorkspaceDirectory = (userPath) => { ); }; -module.exports = (config, context) => { +module.exports = ({ config, context }) => { // dynamically map all the user's workspace directories for resolution by webpack // this essentially helps us keep watch over changes from the user, and greenwood's build pipeline const mappedUserDirectoriesForWebpack = getUserWorkspaceDirectories(context.userWorkspace).map(mapUserWorkspaceDirectory); diff --git a/packages/cli/config/webpack.config.develop.js b/packages/cli/config/webpack.config.develop.js index 833585fea..50b9ad8c9 100644 --- a/packages/cli/config/webpack.config.develop.js +++ b/packages/cli/config/webpack.config.develop.js @@ -23,7 +23,7 @@ const rebuild = async() => { }; module.exports = ({ config, context, graph }) => { - const configWithContext = commonConfig(config, context, graph); + const configWithContext = commonConfig({ config, context, graph }); const { devServer, publicPath } = config; const { host, port } = devServer; diff --git a/packages/cli/config/webpack.config.prod.js b/packages/cli/config/webpack.config.prod.js index cb065dea8..971664fb6 100644 --- a/packages/cli/config/webpack.config.prod.js +++ b/packages/cli/config/webpack.config.prod.js @@ -4,7 +4,7 @@ const webpackMerge = require('webpack-merge'); const commonConfig = require(path.join(__dirname, '..', './config/webpack.config.common.js')); module.exports = ({ config, context, graph }) => { - const configWithContext = commonConfig(config, context, graph); + const configWithContext = commonConfig({ config, context, graph }); return webpackMerge(configWithContext, { diff --git a/packages/cli/lib/compile.js b/packages/cli/lib/compile.js index 31c949303..d999086b8 100644 --- a/packages/cli/lib/compile.js +++ b/packages/cli/lib/compile.js @@ -24,7 +24,7 @@ module.exports = generateCompilation = () => { // generate a graph of all pages / components to build console.log('Generating graph of workspace files...'); - compilation.graph = await generateGraph(compilation); + compilation = await generateGraph(compilation); // generate scaffolding console.log('Scaffolding out project files...'); diff --git a/packages/cli/lib/config.js b/packages/cli/lib/config.js index d4014206f..335ab015b 100644 --- a/packages/cli/lib/config.js +++ b/packages/cli/lib/config.js @@ -9,8 +9,6 @@ let config = { host: 'http://localhost' }, publicPath: '/', - // TODO add global meta data see issue #5 - // https://github.com/ProjectEvergreen/greenwood/issues/5 meta: { title: '', description: '', @@ -27,7 +25,7 @@ module.exports = readAndMergeConfig = async() => { if (fs.existsSync(path.join(process.cwd(), 'greenwood.config.js'))) { const userCfgFile = require(path.join(process.cwd(), 'greenwood.config.js')); - const { workspace, devServer, publicPath } = userCfgFile; + const { workspace, devServer, publicPath, meta, title } = userCfgFile; if (workspace) { if (typeof workspace !== 'string') { @@ -48,6 +46,13 @@ module.exports = readAndMergeConfig = async() => { } } + if (title) { + if (typeof title !== 'string') { + reject('Error: greenwood.config.js title must be a string'); + } + customConfig.title = title; + } + if (publicPath) { if (typeof publicPath !== 'string') { reject('Error: greenwood.config.js publicPath must be a string'); @@ -57,6 +62,10 @@ module.exports = readAndMergeConfig = async() => { } } + if (meta && meta.length > 0) { + customConfig.meta = meta; + } + if (devServer && Object.keys(devServer).length > 0) { if (devServer.host) { diff --git a/packages/cli/lib/graph.js b/packages/cli/lib/graph.js index d0e1c0b8c..6262b7c67 100644 --- a/packages/cli/lib/graph.js +++ b/packages/cli/lib/graph.js @@ -5,7 +5,7 @@ const fm = require('front-matter'); const path = require('path'); const util = require('util'); -const createGraphFromPages = async (pagesDir) => { +const createGraphFromPages = async (pagesDir, config) => { let pages = []; const readdir = util.promisify(fs.readdir); const readFile = util.promisify(fs.readFile); @@ -26,7 +26,8 @@ const createGraphFromPages = async (pagesDir) => { if (isMdFile && !stats.isDirectory()) { const fileContents = await readFile(filePath, 'utf8'); const { attributes } = fm(fileContents); - let { label, template } = attributes; + let { label, template, title } = attributes; + let { meta } = config; let mdFile = ''; // if template not set, use default @@ -54,8 +55,8 @@ const createGraphFromPages = async (pagesDir) => { // set route to the nested pages path and file name(without extension) route = completeNestedPath + route; - mdFile = `./${completeNestedPath}${fileRoute}.md`; - relativeExpectedPath = `'../${completeNestedPath}/${fileName}/${fileName}.js'`; + mdFile = `.${completeNestedPath}${fileRoute}.md`; + relativeExpectedPath = `'..${completeNestedPath}/${fileName}/${fileName}.js'`; } else { mdFile = `.${fileRoute}.md`; relativeExpectedPath = `'../${fileName}/${fileName}.js'`; @@ -64,6 +65,11 @@ const createGraphFromPages = async (pagesDir) => { // generate a random element name label = label || generateLabelHash(filePath); + // set element text, override with markdown title + title = title || config.title; + + // TODO: Allow for other, per page, dynamic, meta data, merge meta array + /* * Variable Definitions *---------------------- @@ -75,10 +81,11 @@ const createGraphFromPages = async (pagesDir) => { * fileName: file name without extension/path, so that it can be copied to scratch dir with same name * relativeExpectedPath: relative import path for generated component within a list.js file to later be * imported into app.js root component - * elementLabel: the element name for the generated md page e.g. + * title: the head text + * meta: og graph meta array of objects { property/name, content } */ - pages.push({ mdFile, label, route, template, filePath, fileName, relativeExpectedPath }); + pages.push({ mdFile, label, route, template, filePath, fileName, relativeExpectedPath, title, meta }); } if (stats.isDirectory()) { await walkDirectory(filePath); @@ -116,9 +123,11 @@ module.exports = generateGraph = async (compilation) => { return new Promise(async (resolve, reject) => { try { - const graph = await createGraphFromPages(compilation.context.pagesDir); + const { context, config } = compilation; + + compilation.graph = await createGraphFromPages(context.pagesDir, config); - resolve(graph); + resolve(compilation); } catch (err) { reject(err); } diff --git a/packages/cli/lib/init.js b/packages/cli/lib/init.js index d6fa29040..85c8a7e6d 100644 --- a/packages/cli/lib/init.js +++ b/packages/cli/lib/init.js @@ -3,6 +3,7 @@ const path = require('path'); const defaultTemplatesDir = path.join(__dirname, '../templates/'); const scratchDir = path.join(process.cwd(), './.greenwood/'); const publicDir = path.join(process.cwd(), './public'); +const metaComponent = path.join(__dirname, '..', 'templates', './components/meta'); module.exports = initContexts = async({ config }) => { @@ -34,7 +35,8 @@ module.exports = initContexts = async({ config }) => { ? userAppTemplate : path.join(defaultTemplatesDir, 'app-template.js'), indexPageTemplate: 'index.html', - notFoundPageTemplate: '404.html' + notFoundPageTemplate: '404.html', + metaComponent }; if (!fs.existsSync(scratchDir)) { diff --git a/packages/cli/lib/scaffold.js b/packages/cli/lib/scaffold.js index 848d44715..1e04b730f 100644 --- a/packages/cli/lib/scaffold.js +++ b/packages/cli/lib/scaffold.js @@ -23,6 +23,32 @@ const writePageComponentsFromTemplate = async (compilation) => { }); }; + const loadPageMeta = async (file, result, context) => { + return new Promise((resolve, reject) => { + try { + const { title, meta } = file; + const metadata = { + title, + meta + }; + + metadata.meta['og:title'] = title; + + let metaComponent = context.metaComponent; + + console.log(metaComponent); + + result = result.replace(/METAIMPORT/, `import '${metaComponent}'`); + result = result.replace(/METADATA/, `const metadata = ${JSON.stringify(metadata)}`); + result = result.replace(/METAELEMENT/, ''); + + resolve(result); + } catch (err) { + reject(err); + } + }); + }; + return Promise.all(compilation.graph.map(file => { const context = compilation.context; @@ -30,6 +56,7 @@ const writePageComponentsFromTemplate = async (compilation) => { try { let result = await createPageComponent(file, context); + result = await loadPageMeta(file, result, context); let relPageDir = file.filePath.substring(context.pagesDir.length, file.filePath.length); const pathLastBackslash = relPageDir.lastIndexOf('/'); diff --git a/packages/cli/templates/components/meta.js b/packages/cli/templates/components/meta.js new file mode 100644 index 000000000..ac3288c5e --- /dev/null +++ b/packages/cli/templates/components/meta.js @@ -0,0 +1,50 @@ +import { html, LitElement } from 'lit-element'; + +class meta extends LitElement { + + static get properties() { + return { + attributes: { + type: Object + } + }; + } + + firstUpdated() { + let header = document.head; + let meta; + + if (this.attributes) { + this.attributes.meta.map(attr => { + meta = document.createElement('meta'); + meta.setAttribute(Object.keys(attr)[0], Object.values(attr)[0]); + meta.setAttribute(Object.keys(attr)[1], Object.values(attr)[1]); + + const oldmeta = header.querySelector(`[${Object.keys(attr)[0]}="${Object.values(attr)[0]}"]`); + + if (oldmeta) { + header.replaceChild(meta, oldmeta); + } else { + header.appendChild(meta); + } + }); + let title = document.createElement('title'); + + title.innerText = this.attributes.title; + const oldTitle = document.head.querySelector('title'); + + header.replaceChild(title, oldTitle); + } + + } + + render() { + return html` +
+ +
+ `; + } +} + +customElements.define('eve-meta', meta); \ No newline at end of file diff --git a/packages/cli/templates/page-template.js b/packages/cli/templates/page-template.js index 16ff0fe5a..4355f1987 100644 --- a/packages/cli/templates/page-template.js +++ b/packages/cli/templates/page-template.js @@ -1,9 +1,12 @@ import { html, LitElement } from 'lit-element'; MDIMPORT; +METAIMPORT; +METADATA; class PageTemplate extends LitElement { render() { return html` + METAELEMENT
diff --git a/test/fixtures/mock-app/src/components/meta.js b/test/fixtures/mock-app/src/components/meta.js deleted file mode 100644 index 3105c2d1b..000000000 --- a/test/fixtures/mock-app/src/components/meta.js +++ /dev/null @@ -1,33 +0,0 @@ -import { html, LitElement } from 'lit-element'; - -class meta extends LitElement { - - static get properties() { - return { - attributes: { - type: Object - } - }; - } - - firstUpdated() { - let header = document.head; - let meta; - - this.attributes.map((attr) => { - meta = document.createElement('meta'); - meta.setAttribute(attr[0], attr[1]); - header.appendChild(meta); - }); - } - - render() { - return html` -
- -
- `; - } -} - -customElements.define('eve-meta', meta); \ No newline at end of file From ca68b406f340f18d2a23342391764eed6abd7bf7 Mon Sep 17 00:00:00 2001 From: HutchGrant Date: Wed, 8 May 2019 20:47:54 -0400 Subject: [PATCH 05/18] fix: remove leftovers from merge --- packages/cli/lib/config.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/cli/lib/config.js b/packages/cli/lib/config.js index 6924b6614..82753de5b 100644 --- a/packages/cli/lib/config.js +++ b/packages/cli/lib/config.js @@ -86,13 +86,6 @@ module.exports = readAndMergeConfig = async() => { } } - if (meta && meta.length > 0) { - customConfig.meta = meta; - } - - if (title) { - customConfig.title = title; - } config = { ...config, ...customConfig }; } From ba67bad1e0925e7459184d2b5f964dd0a459cd7c Mon Sep 17 00:00:00 2001 From: HutchGrant Date: Wed, 8 May 2019 21:04:01 -0400 Subject: [PATCH 06/18] fix: refactor --- packages/cli/lib/config.js | 1 + packages/cli/templates/components/meta.js | 26 ++++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/cli/lib/config.js b/packages/cli/lib/config.js index 82753de5b..b702ba62d 100644 --- a/packages/cli/lib/config.js +++ b/packages/cli/lib/config.js @@ -9,6 +9,7 @@ let config = { host: 'http://localhost' }, publicPath: '/', + title: 'Greenwood App', meta: [] }; diff --git a/packages/cli/templates/components/meta.js b/packages/cli/templates/components/meta.js index 6abb45c1d..7e010b1a6 100644 --- a/packages/cli/templates/components/meta.js +++ b/packages/cli/templates/components/meta.js @@ -1,5 +1,13 @@ import { html, LitElement } from 'lit-element'; +/* +* Take an array of meta objects, add them to an element and replace/add the element to DOM +* meta: [ +* { property: 'og:site', content: 'greenwood' }, +* { name: 'twitter:site', content: '@PrjEvergreen ' } +* ] +*/ + class meta extends LitElement { static get properties() { @@ -18,21 +26,19 @@ class meta extends LitElement { this.attributes.meta.map(attr => { meta = document.createElement('meta'); - const metaKey1 = Object.keys(attr)[0]; - const metaVal1 = Object.values(attr)[0]; - - const metaKey2 = Object.keys(attr)[1]; - let metaVal2 = Object.values(attr)[1]; + const metaPropertyOrName = Object.keys(attr)[0]; + const metaPropValue = Object.values(attr)[0]; + let metaContentVal = Object.values(attr)[1]; // insert origin domain into url - if (metaVal1 === 'og:url') { - metaVal2 = window.location.origin + metaVal2; + if (metaPropValue === 'og:url') { + metaContentVal = window.location.origin + metaContentVal; } - meta.setAttribute(metaKey1, metaVal1); - meta.setAttribute(metaKey2, metaVal2); + meta.setAttribute(metaPropertyOrName, metaPropValue); + meta.setAttribute('content', metaContentVal); - const oldmeta = header.querySelector(`[${Object.keys(attr)[0]}="${Object.values(attr)[0]}"]`); + const oldmeta = header.querySelector(`[${metaPropertyOrName}="${metaPropValue}"]`); // rehydration if (oldmeta) { From 647cc83467c103214858c932070550451c8b27ce Mon Sep 17 00:00:00 2001 From: HutchGrant Date: Wed, 8 May 2019 21:16:07 -0400 Subject: [PATCH 07/18] fix: update meta summary comment --- packages/cli/templates/components/meta.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/cli/templates/components/meta.js b/packages/cli/templates/components/meta.js index 7e010b1a6..d9885ce66 100644 --- a/packages/cli/templates/components/meta.js +++ b/packages/cli/templates/components/meta.js @@ -1,11 +1,14 @@ import { html, LitElement } from 'lit-element'; /* -* Take an array of meta objects, add them to an element and replace/add the element to DOM +* Take an attributes object with an array of meta objects, add them to an element and replace/add the element to DOM +* { +* title: 'my title', * meta: [ * { property: 'og:site', content: 'greenwood' }, * { name: 'twitter:site', content: '@PrjEvergreen ' } * ] +* } */ class meta extends LitElement { From fe4a7b4986029f3fa16b160ac022d1c5e38992e4 Mon Sep 17 00:00:00 2001 From: HutchGrant Date: Wed, 8 May 2019 21:28:50 -0400 Subject: [PATCH 08/18] test: adding tests for title and meta --- test/cli.spec.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/cli.spec.js b/test/cli.spec.js index c8bf3b395..ef4074eeb 100644 --- a/test/cli.spec.js +++ b/test/cli.spec.js @@ -418,6 +418,37 @@ describe('building greenwood with user provided config file', () => { }); }); + describe('array of meta objects and title', () => { + + const appTitle = 'Mock App'; + const appMeta = [ + { property: 'og:site', content: 'greenwood' }, + { name: 'twitter:site', content: '@PrjEvergreen ' } + ]; + + beforeEach(async() => { + dom = await JSDOM.fromFile(blogPageHtmlPath); + }); + + it('should contain the correct title in head', () => { + const title = dom.window.document.head.querySelector('title').textContent; + + expect(title).to.equal(appTitle); + }); + + it('should contain the meta element with correct property and content', () => { + const content = dom.window.document.head.querySelector('[property="og:site"]').getAttribute('content'); + + expect(content).to.equal(appMeta[0].content); + }); + + it('should contain the meta element with correct name and content', () => { + const content = dom.window.document.head.querySelector('[name="twitter:site"]').getAttribute('content'); + + expect(content).to.equal(appMeta[1].content); + }); + }); + after(async() => { await fs.remove(CONTEXT.userSrc); await fs.remove(CONTEXT.userCfgRootPath); From 2afc21adb140bacb99caa5eead26602c5738fec4 Mon Sep 17 00:00:00 2001 From: HutchGrant Date: Wed, 8 May 2019 21:37:12 -0400 Subject: [PATCH 09/18] test: adding more tests for og:url and og:title --- test/cli.spec.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/cli.spec.js b/test/cli.spec.js index ef4074eeb..20f2f993f 100644 --- a/test/cli.spec.js +++ b/test/cli.spec.js @@ -425,6 +425,7 @@ describe('building greenwood with user provided config file', () => { { property: 'og:site', content: 'greenwood' }, { name: 'twitter:site', content: '@PrjEvergreen ' } ]; + const url = '/blog/20190326/'; beforeEach(async() => { dom = await JSDOM.fromFile(blogPageHtmlPath); @@ -436,6 +437,12 @@ describe('building greenwood with user provided config file', () => { expect(title).to.equal(appTitle); }); + it('should contain the meta element with correct og:title', () => { + const title = dom.window.document.head.querySelector('[property="og:title"]').getAttribute('content'); + + expect(title).to.equal(appTitle); + }); + it('should contain the meta element with correct property and content', () => { const content = dom.window.document.head.querySelector('[property="og:site"]').getAttribute('content'); @@ -447,6 +454,12 @@ describe('building greenwood with user provided config file', () => { expect(content).to.equal(appMeta[1].content); }); + + it('should contain the meta element with correct og:url', () => { + const content = dom.window.document.head.querySelector('[property="og:url"]').getAttribute('content'); + + expect(content).to.contain(url); + }); }); after(async() => { From b772c9af012465090a33e4ebc0e12538a2094c3f Mon Sep 17 00:00:00 2001 From: HutchGrant Date: Wed, 8 May 2019 21:46:41 -0400 Subject: [PATCH 10/18] fix: small refactor --- packages/cli/lib/init.js | 1 - packages/cli/lib/scaffold.js | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/cli/lib/init.js b/packages/cli/lib/init.js index 502283e90..85c8a7e6d 100644 --- a/packages/cli/lib/init.js +++ b/packages/cli/lib/init.js @@ -23,7 +23,6 @@ module.exports = initContexts = async({ config }) => { const userHasWorkspaceAppTemplate = fs.existsSync(userAppTemplate); let context = { - defaultTemplatesDir, scratchDir, publicDir, pagesDir: userHasWorkspacePages ? userPagesDir : defaultTemplatesDir, diff --git a/packages/cli/lib/scaffold.js b/packages/cli/lib/scaffold.js index 7a1cfb193..1a437a87b 100644 --- a/packages/cli/lib/scaffold.js +++ b/packages/cli/lib/scaffold.js @@ -26,18 +26,16 @@ const writePageComponentsFromTemplate = async (compilation) => { const loadPageMeta = async (file, result, context) => { return new Promise((resolve, reject) => { try { - const { title, meta } = file; + const { title, meta, route } = file; const metadata = { title, meta }; metadata.meta.push({ property: 'og:title', content: title }); - metadata.meta.push({ property: 'og:url', content: file.route }); - - let metaComponent = context.metaComponent; + metadata.meta.push({ property: 'og:url', content: route }); - result = result.replace(/METAIMPORT/, `import '${metaComponent}'`); + result = result.replace(/METAIMPORT/, `import '${context.metaComponent}'`); result = result.replace(/METADATA/, `const metadata = ${JSON.stringify(metadata)}`); result = result.replace(/METAELEMENT/, ''); From 3361a740c548b9d2c32c87d5176d89237c27c1fe Mon Sep 17 00:00:00 2001 From: HutchGrant Date: Wed, 8 May 2019 21:48:42 -0400 Subject: [PATCH 11/18] fix: small refactor --- packages/cli/lib/scaffold.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/lib/scaffold.js b/packages/cli/lib/scaffold.js index 1a437a87b..930a1f3d5 100644 --- a/packages/cli/lib/scaffold.js +++ b/packages/cli/lib/scaffold.js @@ -23,7 +23,7 @@ const writePageComponentsFromTemplate = async (compilation) => { }); }; - const loadPageMeta = async (file, result, context) => { + const loadPageMeta = async (file, result, { metaComponent }) => { return new Promise((resolve, reject) => { try { const { title, meta, route } = file; @@ -35,7 +35,7 @@ const writePageComponentsFromTemplate = async (compilation) => { metadata.meta.push({ property: 'og:title', content: title }); metadata.meta.push({ property: 'og:url', content: route }); - result = result.replace(/METAIMPORT/, `import '${context.metaComponent}'`); + result = result.replace(/METAIMPORT/, `import '${metaComponent}'`); result = result.replace(/METADATA/, `const metadata = ${JSON.stringify(metadata)}`); result = result.replace(/METAELEMENT/, ''); From b10607dd9841bba662ba1c8573e27993b2761cd8 Mon Sep 17 00:00:00 2001 From: HutchGrant Date: Sat, 11 May 2019 19:42:33 -0400 Subject: [PATCH 12/18] test: adding meta config tests --- .../build.config.meta.spec.js | 161 ++++++++++++++++++ .../build.config.meta/greenwood.config.js | 7 + .../build.config.meta/src/pages/hello.md | 7 + .../build.config.meta/src/pages/index.md | 3 + 4 files changed, 178 insertions(+) create mode 100644 test/cli/cases/build.config.meta/build.config.meta.spec.js create mode 100644 test/cli/cases/build.config.meta/greenwood.config.js create mode 100644 test/cli/cases/build.config.meta/src/pages/hello.md create mode 100644 test/cli/cases/build.config.meta/src/pages/index.md diff --git a/test/cli/cases/build.config.meta/build.config.meta.spec.js b/test/cli/cases/build.config.meta/build.config.meta.spec.js new file mode 100644 index 000000000..8db666c3b --- /dev/null +++ b/test/cli/cases/build.config.meta/build.config.meta.spec.js @@ -0,0 +1,161 @@ +/* + * Use Case + * Run Greenwood with meta config object and default workspace. + * + * User Result + * Should generate a bare bones Greenwood build. (same as build.default.spec.js) with meta data + * + * User Command + * greenwood build + * + * User Config + * {} + * + * User Workspace + * Greenwood default + * src/ + * pages/ + * index.md + * hello.md + */ +const fs = require('fs'); +const { JSDOM } = require('jsdom'); +const path = require('path'); +const expect = require('chai').expect; +const runSmokeTest = require('../../smoke-test'); +const TestBed = require('../../test-bed'); + +// TODO why does this case need a src/pages/index.md? +describe('Build Greenwood With: ', async function() { + const LABEL = 'Custom Meta Configuration and Default Workspace'; + let setup; + + before(async function() { + setup = new TestBed(); + this.context = setup.setupTestBed(__dirname); + }); + + describe(LABEL, function() { + before(async function() { + await setup.runGreenwoodCommand('build'); + }); + runSmokeTest(['public', 'not-found'], LABEL); + + describe('Custom Meta Index Page', function() { + const indexPageTitle = 'My Custom Greenwood App'; + const indexPageHeading = 'Greenwood'; + const indexPageBody = 'This is the home page built by Greenwood. Make your own pages in src/pages/index.js!'; + let dom; + + beforeEach(async function() { + dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './index.html')); + }); + + it('should output an index.html file within the default hello page directory', function() { + expect(fs.existsSync(path.join(this.context.publicDir, './index.html'))).to.be.true; + }); + + it('should have our custom config meta tag in the <head>', function() { + const title = dom.window.document.querySelector('head title').textContent; + + expect(title).to.be.equal(indexPageTitle); + }); + + it('should have our custom config <meta> tag with og:site property in the <head>', function() { + const metaElement = dom.window.document.querySelector('head meta[property="og:site"]'); + + expect(metaElement.getAttribute('content')).to.be.equal('greenwood'); + }); + + it('should have our custom config <meta> tag with og:site property in the <head>', function() { + const metaElement = dom.window.document.querySelector('head meta[name="twitter:site"]'); + + expect(metaElement.getAttribute('content')).to.be.equal('@PrjEvergreen'); + }); + + it('should have a <script> tag in the <body>', function() { + const scriptTag = dom.window.document.querySelectorAll('body script'); + + expect(scriptTag.length).to.be.equal(1); + }); + + it('should have a router outlet tag in the <body>', function() { + const outlet = dom.window.document.querySelectorAll('body eve-app'); + + expect(outlet.length).to.be.equal(1); + }); + + it('should have the correct route tags in the <body>', function() { + const routes = dom.window.document.querySelectorAll('body lit-route'); + + expect(routes.length).to.be.equal(3); + }); + + it('should have the expected heading text within the index page in the public directory', function() { + const heading = dom.window.document.querySelector('h3').textContent; + + expect(heading).to.equal(indexPageHeading); + }); + + it('should have the expected paragraph text within the index page in the public directory', function() { + let paragraph = dom.window.document.querySelector('p').textContent; + + expect(paragraph).to.equal(indexPageBody); + }); + }); + + describe('Custom Meta Hello Page w/ Front-Matter Title Override', function() { + const helloPageTitle = 'Hello Page'; + const helloPageHeading = 'Hello World'; + const helloPageBody = 'This is an example page built by Greenwood. Make your own in src/pages!'; + let dom; + + beforeEach(async function() { + dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './hello', './index.html')); + }); + + it('should output a hello page directory', function() { + expect(fs.existsSync(path.join(this.context.publicDir, './hello'))).to.be.true; + }); + + it('should output an index.html file within the default hello page directory', function() { + expect(fs.existsSync(path.join(this.context.publicDir, './hello', './index.html'))).to.be.true; + }); + + it('should have a overridden meta <title> tag in the <head> using markdown front-matter', function() { + const title = dom.window.document.querySelector('head title').textContent; + + expect(title).to.be.equal(helloPageTitle); + }); + + it('should have our custom config <meta> tag with og:site property in the <head>', function() { + const metaElement = dom.window.document.querySelector('head meta[property="og:site"]'); + + expect(metaElement.getAttribute('content')).to.be.equal('greenwood'); + }); + + it('should have our custom config <meta> tag with og:site property in the <head>', function() { + const metaElement = dom.window.document.querySelector('head meta[name="twitter:site"]'); + + expect(metaElement.getAttribute('content')).to.be.equal('@PrjEvergreen'); + }); + + it('should have the expected heading text within the hello example page in the hello directory', function() { + const heading = dom.window.document.querySelector('h3').textContent; + + expect(heading).to.equal(helloPageHeading); + }); + + it('should have the expected paragraph text within the hello example page in the hello directory', function() { + let paragraph = dom.window.document.querySelector('p').textContent; + + expect(paragraph).to.equal(helloPageBody); + }); + + }); + }); + + after(function() { + // setup.teardownTestBed(); + }); +}); \ No newline at end of file diff --git a/test/cli/cases/build.config.meta/greenwood.config.js b/test/cli/cases/build.config.meta/greenwood.config.js new file mode 100644 index 000000000..ed94fc1bd --- /dev/null +++ b/test/cli/cases/build.config.meta/greenwood.config.js @@ -0,0 +1,7 @@ +module.exports = { + title: 'My Custom Greenwood App', + meta: [ + { property: 'og:site', content: 'greenwood' }, + { name: 'twitter:site', content: '@PrjEvergreen' } + ] +}; \ No newline at end of file diff --git a/test/cli/cases/build.config.meta/src/pages/hello.md b/test/cli/cases/build.config.meta/src/pages/hello.md new file mode 100644 index 000000000..fb8cf32d5 --- /dev/null +++ b/test/cli/cases/build.config.meta/src/pages/hello.md @@ -0,0 +1,7 @@ +--- +label: 'hello' +title: 'Hello Page' +--- +### Hello World + +This is an example page built by Greenwood. Make your own in _src/pages_! \ No newline at end of file diff --git a/test/cli/cases/build.config.meta/src/pages/index.md b/test/cli/cases/build.config.meta/src/pages/index.md new file mode 100644 index 000000000..1c1a50fbb --- /dev/null +++ b/test/cli/cases/build.config.meta/src/pages/index.md @@ -0,0 +1,3 @@ +### Greenwood + +This is the home page built by Greenwood. Make your own pages in src/pages/index.js! \ No newline at end of file From 3de78781be29d9e1f282c6bcb88c78079febebda Mon Sep 17 00:00:00 2001 From: HutchGrant <h.g.utchinson@gmail.com> Date: Sat, 11 May 2019 19:46:51 -0400 Subject: [PATCH 13/18] fix: cleanup test description and summary --- .../cases/build.config.meta/build.config.meta.spec.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/cli/cases/build.config.meta/build.config.meta.spec.js b/test/cli/cases/build.config.meta/build.config.meta.spec.js index 8db666c3b..93f344b05 100644 --- a/test/cli/cases/build.config.meta/build.config.meta.spec.js +++ b/test/cli/cases/build.config.meta/build.config.meta.spec.js @@ -9,7 +9,13 @@ * greenwood build * * User Config - * {} + * { + * title: 'My Custom Greenwood App', + * meta: [ + * { property: 'og:site', content: 'greenwood' }, + * { name: 'twitter:site', content: '@PrjEvergreen' } + * ] + * } * * User Workspace * Greenwood default @@ -67,7 +73,7 @@ describe('Build Greenwood With: ', async function() { expect(metaElement.getAttribute('content')).to.be.equal('greenwood'); }); - it('should have our custom config <meta> tag with og:site property in the <head>', function() { + it('should have our custom config <meta> tag with twitter:site name in the <head>', function() { const metaElement = dom.window.document.querySelector('head meta[name="twitter:site"]'); expect(metaElement.getAttribute('content')).to.be.equal('@PrjEvergreen'); From 0599e68b9fa89157eba83463ce58b8d822a951b8 Mon Sep 17 00:00:00 2001 From: HutchGrant <h.g.utchinson@gmail.com> Date: Sat, 11 May 2019 19:55:00 -0400 Subject: [PATCH 14/18] fix: re-enable meta teardown --- test/cli/cases/build.config.meta/build.config.meta.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cli/cases/build.config.meta/build.config.meta.spec.js b/test/cli/cases/build.config.meta/build.config.meta.spec.js index 93f344b05..88840c556 100644 --- a/test/cli/cases/build.config.meta/build.config.meta.spec.js +++ b/test/cli/cases/build.config.meta/build.config.meta.spec.js @@ -162,6 +162,6 @@ describe('Build Greenwood With: ', async function() { }); after(function() { - // setup.teardownTestBed(); + setup.teardownTestBed(); }); }); \ No newline at end of file From b475ba58d700c5b2df1da3baac5f9e24942e7b37 Mon Sep 17 00:00:00 2001 From: HutchGrant <h.g.utchinson@gmail.com> Date: Tue, 14 May 2019 21:14:44 -0400 Subject: [PATCH 15/18] test: seperating meta and title tests, modifying smoke-tests, adding title config error test --- .../build.config.default.spec.js | 2 +- .../build.config.error-title.spec.js | 44 +++++++ .../greenwood.config.js | 3 + .../build.config.meta.spec.js | 95 +------------- .../build.config.meta/greenwood.config.js | 1 - .../build.config.title.spec.js | 124 ++++++++++++++++++ .../build.config.title/greenwood.config.js | 3 + .../build.config.title/src/pages/hello.md | 7 + .../build.config.title/src/pages/index.md | 3 + .../build.config.workspace-custom.spec.js | 2 +- .../build.default.workspace-nested.spec.js | 2 +- ...ild.default.workspace-template-app.spec.js | 2 +- ...ld.default.workspace-template-page.spec.js | 2 +- .../cases/build.default/build.default.spec.js | 2 +- test/cli/smoke-test.js | 27 ++++ 15 files changed, 219 insertions(+), 100 deletions(-) create mode 100644 test/cli/cases/build.config.error-title/build.config.error-title.spec.js create mode 100644 test/cli/cases/build.config.error-title/greenwood.config.js create mode 100644 test/cli/cases/build.config.title/build.config.title.spec.js create mode 100644 test/cli/cases/build.config.title/greenwood.config.js create mode 100644 test/cli/cases/build.config.title/src/pages/hello.md create mode 100644 test/cli/cases/build.config.title/src/pages/index.md diff --git a/test/cli/cases/build.config.default/build.config.default.spec.js b/test/cli/cases/build.config.default/build.config.default.spec.js index 0c4fa90a3..4f056bd51 100644 --- a/test/cli/cases/build.config.default/build.config.default.spec.js +++ b/test/cli/cases/build.config.default/build.config.default.spec.js @@ -30,7 +30,7 @@ describe('Build Greenwood With: ', async function() { before(async function() { await setup.runGreenwoodCommand('build'); }); - runSmokeTest(['public', 'index', 'not-found', 'hello'], LABEL); + runSmokeTest(['public', 'index', 'not-found', 'hello', 'meta'], LABEL); }); after(function() { diff --git a/test/cli/cases/build.config.error-title/build.config.error-title.spec.js b/test/cli/cases/build.config.error-title/build.config.error-title.spec.js new file mode 100644 index 000000000..9ac5a145f --- /dev/null +++ b/test/cli/cases/build.config.error-title/build.config.error-title.spec.js @@ -0,0 +1,44 @@ +/* + * Use Case + * Run Greenwood build command with a bad value for title in a custom config. + * + * User Result + * Should throw an error. + * + * User Command + * greenwood build + * + * User Config + * { + * title: {} + * } + * + * User Workspace + * Greenwood default + */ +const expect = require('chai').expect; +const TestBed = require('../../test-bed'); + +describe('Build Greenwood With: ', () => { + let setup; + + before(async () => { + setup = new TestBed(); + setup.setupTestBed(__dirname); + }); + + describe('Custom Configuration with a bad value for Title', () => { + it('should throw an error that title must be a string', async () => { + try { + await setup.runGreenwoodCommand('build'); + } catch (err) { + expect(err).to.contain('greenwood.config.js title must be a string'); + } + }); + }); + + after(function() { + setup.teardownTestBed(); + }); + +}); \ No newline at end of file diff --git a/test/cli/cases/build.config.error-title/greenwood.config.js b/test/cli/cases/build.config.error-title/greenwood.config.js new file mode 100644 index 000000000..e76c49235 --- /dev/null +++ b/test/cli/cases/build.config.error-title/greenwood.config.js @@ -0,0 +1,3 @@ +module.exports = { + title: {} +}; \ No newline at end of file diff --git a/test/cli/cases/build.config.meta/build.config.meta.spec.js b/test/cli/cases/build.config.meta/build.config.meta.spec.js index 88840c556..ba3a341b0 100644 --- a/test/cli/cases/build.config.meta/build.config.meta.spec.js +++ b/test/cli/cases/build.config.meta/build.config.meta.spec.js @@ -31,7 +31,6 @@ const expect = require('chai').expect; const runSmokeTest = require('../../smoke-test'); const TestBed = require('../../test-bed'); -// TODO why does this case need a src/pages/index.md? describe('Build Greenwood With: ', async function() { const LABEL = 'Custom Meta Configuration and Default Workspace'; let setup; @@ -45,12 +44,9 @@ describe('Build Greenwood With: ', async function() { before(async function() { await setup.runGreenwoodCommand('build'); }); - runSmokeTest(['public', 'not-found'], LABEL); + runSmokeTest(['public', 'index', 'not-found', 'hello', 'meta'], LABEL); describe('Custom Meta Index Page', function() { - const indexPageTitle = 'My Custom Greenwood App'; - const indexPageHeading = 'Greenwood'; - const indexPageBody = 'This is the home page built by Greenwood. Make your own pages in src/pages/index.js!'; let dom; beforeEach(async function() { @@ -61,12 +57,6 @@ describe('Build Greenwood With: ', async function() { expect(fs.existsSync(path.join(this.context.publicDir, './index.html'))).to.be.true; }); - it('should have our custom config meta <title> tag in the <head>', function() { - const title = dom.window.document.querySelector('head title').textContent; - - expect(title).to.be.equal(indexPageTitle); - }); - it('should have our custom config <meta> tag with og:site property in the <head>', function() { const metaElement = dom.window.document.querySelector('head meta[property="og:site"]'); @@ -78,89 +68,8 @@ describe('Build Greenwood With: ', async function() { expect(metaElement.getAttribute('content')).to.be.equal('@PrjEvergreen'); }); - - it('should have a <script> tag in the <body>', function() { - const scriptTag = dom.window.document.querySelectorAll('body script'); - - expect(scriptTag.length).to.be.equal(1); - }); - - it('should have a router outlet tag in the <body>', function() { - const outlet = dom.window.document.querySelectorAll('body eve-app'); - - expect(outlet.length).to.be.equal(1); - }); - - it('should have the correct route tags in the <body>', function() { - const routes = dom.window.document.querySelectorAll('body lit-route'); - - expect(routes.length).to.be.equal(3); - }); - - it('should have the expected heading text within the index page in the public directory', function() { - const heading = dom.window.document.querySelector('h3').textContent; - - expect(heading).to.equal(indexPageHeading); - }); - - it('should have the expected paragraph text within the index page in the public directory', function() { - let paragraph = dom.window.document.querySelector('p').textContent; - - expect(paragraph).to.equal(indexPageBody); - }); }); - - describe('Custom Meta Hello Page w/ Front-Matter Title Override', function() { - const helloPageTitle = 'Hello Page'; - const helloPageHeading = 'Hello World'; - const helloPageBody = 'This is an example page built by Greenwood. Make your own in src/pages!'; - let dom; - - beforeEach(async function() { - dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './hello', './index.html')); - }); - - it('should output a hello page directory', function() { - expect(fs.existsSync(path.join(this.context.publicDir, './hello'))).to.be.true; - }); - - it('should output an index.html file within the default hello page directory', function() { - expect(fs.existsSync(path.join(this.context.publicDir, './hello', './index.html'))).to.be.true; - }); - - it('should have a overridden meta <title> tag in the <head> using markdown front-matter', function() { - const title = dom.window.document.querySelector('head title').textContent; - - expect(title).to.be.equal(helloPageTitle); - }); - - it('should have our custom config <meta> tag with og:site property in the <head>', function() { - const metaElement = dom.window.document.querySelector('head meta[property="og:site"]'); - - expect(metaElement.getAttribute('content')).to.be.equal('greenwood'); - }); - - it('should have our custom config <meta> tag with og:site property in the <head>', function() { - const metaElement = dom.window.document.querySelector('head meta[name="twitter:site"]'); - - expect(metaElement.getAttribute('content')).to.be.equal('@PrjEvergreen'); - }); - - it('should have the expected heading text within the hello example page in the hello directory', function() { - const heading = dom.window.document.querySelector('h3').textContent; - - expect(heading).to.equal(helloPageHeading); - }); - - it('should have the expected paragraph text within the hello example page in the hello directory', function() { - let paragraph = dom.window.document.querySelector('p').textContent; - - expect(paragraph).to.equal(helloPageBody); - }); - - }); - }); - + }); after(function() { setup.teardownTestBed(); }); diff --git a/test/cli/cases/build.config.meta/greenwood.config.js b/test/cli/cases/build.config.meta/greenwood.config.js index ed94fc1bd..9fa0da1bb 100644 --- a/test/cli/cases/build.config.meta/greenwood.config.js +++ b/test/cli/cases/build.config.meta/greenwood.config.js @@ -1,5 +1,4 @@ module.exports = { - title: 'My Custom Greenwood App', meta: [ { property: 'og:site', content: 'greenwood' }, { name: 'twitter:site', content: '@PrjEvergreen' } diff --git a/test/cli/cases/build.config.title/build.config.title.spec.js b/test/cli/cases/build.config.title/build.config.title.spec.js new file mode 100644 index 000000000..9575aae75 --- /dev/null +++ b/test/cli/cases/build.config.title/build.config.title.spec.js @@ -0,0 +1,124 @@ +/* + * Use Case + * Run Greenwood with meta config object and default workspace. + * + * User Result + * Should generate a bare bones Greenwood build. (same as build.default.spec.js) with meta data + * + * User Command + * greenwood build + * + * User Config + * { + * title: 'My Custom Greenwood App', + * meta: [ + * { property: 'og:site', content: 'greenwood' }, + * { name: 'twitter:site', content: '@PrjEvergreen' } + * ] + * } + * + * User Workspace + * Greenwood default + * src/ + * pages/ + * index.md + * hello.md + */ +const fs = require('fs'); +const { JSDOM } = require('jsdom'); +const path = require('path'); +const expect = require('chai').expect; +const runSmokeTest = require('../../smoke-test'); +const TestBed = require('../../test-bed'); + +describe('Build Greenwood With: ', async function() { + const LABEL = 'Custom Title Configuration and Default Workspace'; + let setup; + + before(async function() { + setup = new TestBed(); + this.context = setup.setupTestBed(__dirname); + }); + + describe(LABEL, function() { + before(async function() { + await setup.runGreenwoodCommand('build'); + }); + runSmokeTest(['public', 'not-found', 'hello', 'meta'], LABEL); + + describe('Custom Title', function() { + const indexPageTitle = 'My Custom Greenwood App'; + const indexPageHeading = 'Greenwood'; + const indexPageBody = 'This is the home page built by Greenwood. Make your own pages in src/pages/index.js!'; + let dom; + + beforeEach(async function() { + dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './index.html')); + }); + + it('should output an index.html file within the default public directory', function() { + expect(fs.existsSync(path.join(this.context.publicDir, './index.html'))).to.be.true; + }); + + it('should have our custom config meta <title> tag in the <head>', function() { + const title = dom.window.document.querySelector('head title').textContent; + + expect(title).to.be.equal(indexPageTitle); + }); + + // rest of index smoke-test because <title> is changed for this case + it('should have a