From 43c08eb744f3eb9ff534c28bc656ae85d2ee4665 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Wed, 6 Oct 2021 12:56:08 -0700 Subject: [PATCH 1/3] Allow environment overrides specifically for Storybook Stories are rendered as full Ember Apps including the default Environment which may have settings that aren't appropriate for only rendering a single story in an iframe. Now those can be overridden with a .storybook/environment.js file --- index.js | 19 ++++++++++++++++++- lib/util.js | 6 ++++++ package.json | 3 ++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index b485a40..a7ac5fc 100644 --- a/index.js +++ b/index.js @@ -5,7 +5,7 @@ const path = require('path'); const YUIDocsGenerator = require('ember-cli-addon-docs-yuidoc/lib/broccoli/generator'); const Funnel = require('broccoli-funnel'); const mergeTrees = require('broccoli-merge-trees'); -const { parse, generatePreviewHead } = require('./lib/util'); +const { parse, generatePreviewHead, overrideEnvironment } = require('./lib/util'); module.exports = { name: require('./package').name, @@ -75,6 +75,7 @@ module.exports = { const previewHeadFilePath = path.resolve(process.cwd(), '.storybook/preview-head.html'); const previewHeadDirectory = path.dirname(previewHeadFilePath); const envFilePath = path.resolve(process.cwd(), '.env'); + const environmentOverridePath = path.resolve(process.cwd(), '.storybook/environment.js'); let fileContents = ''; @@ -94,6 +95,22 @@ module.exports = { this.ui.writeDebugLine('Generating preview-head.html'); + const environment = parsedConfig.meta.find(meta => meta.name.endsWith('config/environment')); + + if (environment) { + // From the Ember App's environment.js file + const original = JSON.parse(decodeURIComponent(environment.content)); + + // When rootURL is anything other than "/" routing can't be started without erroring, so + // this is a sensible default. + const defaultOverride = { rootURL: '/' }; + + // Allow arbitrary overriding in the storybook environment + const environmentOverride = fs.existsSync(environmentOverridePath) && require(environmentOverridePath)(process.env); + + environment.content = encodeURIComponent(JSON.stringify(overrideEnvironment(original, defaultOverride, environmentOverride))); + } + if(config) { this.ui.writeDebugLine('Setting up overrides.'); diff --git a/lib/util.js b/lib/util.js index 9dae616..43769a5 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,4 +1,5 @@ const cheerio = require('cheerio'); +const merge = require('lodash.merge'); const lookupTable = { meta: [{ @@ -156,9 +157,14 @@ function generatePreviewHead(parsedConfig) { return doc.join('\n') } +function overrideEnvironment(env, ...overrides) { + return merge(env, ...overrides); +} + module.exports = { getDocumentValues, parse, objectToHTMLAttributes, generatePreviewHead, + overrideEnvironment, }; diff --git a/package.json b/package.json index 8a9d62a..5ed983d 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,8 @@ "cheerio": "^1.0.0-rc.10", "ember-cli-addon-docs-yuidoc": "^1.0.0", "ember-cli-babel": "^7.26.11", - "ember-cli-htmlbars": "^6.0.1" + "ember-cli-htmlbars": "^6.0.1", + "lodash.merge": "^4.6.2" }, "publishConfig": { "access": "public" From f1274fd5eaa6558106c8f4cffef8d029d8c08069 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Sun, 24 Apr 2022 09:45:29 -0700 Subject: [PATCH 2/3] Refactor environment override to make util testing simpler --- index.js | 10 ++++------ lib/util.js | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index a7ac5fc..3c8fb0e 100644 --- a/index.js +++ b/index.js @@ -5,7 +5,7 @@ const path = require('path'); const YUIDocsGenerator = require('ember-cli-addon-docs-yuidoc/lib/broccoli/generator'); const Funnel = require('broccoli-funnel'); const mergeTrees = require('broccoli-merge-trees'); -const { parse, generatePreviewHead, overrideEnvironment } = require('./lib/util'); +const { parse, generatePreviewHead, overrideEnvironment, findEnvironment } = require('./lib/util'); module.exports = { name: require('./package').name, @@ -95,12 +95,9 @@ module.exports = { this.ui.writeDebugLine('Generating preview-head.html'); - const environment = parsedConfig.meta.find(meta => meta.name.endsWith('config/environment')); + const environment = findEnvironment(parsedConfig); if (environment) { - // From the Ember App's environment.js file - const original = JSON.parse(decodeURIComponent(environment.content)); - // When rootURL is anything other than "/" routing can't be started without erroring, so // this is a sensible default. const defaultOverride = { rootURL: '/' }; @@ -108,7 +105,8 @@ module.exports = { // Allow arbitrary overriding in the storybook environment const environmentOverride = fs.existsSync(environmentOverridePath) && require(environmentOverridePath)(process.env); - environment.content = encodeURIComponent(JSON.stringify(overrideEnvironment(original, defaultOverride, environmentOverride))); + // Apply overrides to the environment meta node + environment.content = overrideEnvironment(environment, defaultOverride, environmentOverride); } if(config) { diff --git a/lib/util.js b/lib/util.js index 43769a5..9266a47 100644 --- a/lib/util.js +++ b/lib/util.js @@ -157,8 +157,20 @@ function generatePreviewHead(parsedConfig) { return doc.join('\n') } -function overrideEnvironment(env, ...overrides) { - return merge(env, ...overrides); +function extendEnvironment(env, ...extensions) { + return merge(env, ...extensions); +} + +// Given a parsed config, returns the `config/environment` meta node +function findEnvironment(parsedConfig) { + return parsedConfig.meta.find(meta => meta.name.endsWith('config/environment')); +} + +// In-place mutation of the env meta node to include encoded extensions +function overrideEnvironment(env, ...extensions) { + // From the Ember App's environment.js file + const original = JSON.parse(decodeURIComponent(env.content)); + return encodeURIComponent(JSON.stringify(extendEnvironment(original, ...extensions))); } module.exports = { @@ -166,5 +178,7 @@ module.exports = { parse, objectToHTMLAttributes, generatePreviewHead, + findEnvironment, + extendEnvironment, overrideEnvironment, }; From 57d94f56b38b25d789c6cda17c9cd2fb86a0a64e Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Sun, 24 Apr 2022 10:53:51 -0700 Subject: [PATCH 3/3] Test coverage for the environment override utils --- node-tests/fixtures/no-env.html | 38 +++++++++++ node-tests/util.test.js | 111 +++++++++++++++++++++++++++++--- 2 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 node-tests/fixtures/no-env.html diff --git a/node-tests/fixtures/no-env.html b/node-tests/fixtures/no-env.html new file mode 100644 index 0000000..26e67ee --- /dev/null +++ b/node-tests/fixtures/no-env.html @@ -0,0 +1,38 @@ + + + + + + StorybookEmber31 Tests + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/node-tests/util.test.js b/node-tests/util.test.js index 8e440c0..a72d62c 100644 --- a/node-tests/util.test.js +++ b/node-tests/util.test.js @@ -3,6 +3,9 @@ const path = require('path'); const { generatePreviewHead, objectToHTMLAttributes, + findEnvironment, + overrideEnvironment, + extendEnvironment, parse } = require('../lib/util'); @@ -163,33 +166,123 @@ Object { } `); }); - + it('should be able to parse metas from a built html file from an app that uses engines', () => { expect.assertions(1); - + const fileContent = fs.readFileSync(path.resolve(__dirname, 'fixtures', 'engines.html'), 'utf8'); const metas = parse(fileContent, true).meta; - + expect(metas).toMatchSnapshot(); }); }); - + describe('@generatePreviewHead', () => { it('should work with file created with `ember build`', () => { expect.assertions(1); - + const fileContent = fs.readFileSync(path.resolve(__dirname, 'fixtures', 'build.html'), 'utf8'); - + expect(generatePreviewHead(parse(fileContent))).toMatchSnapshot() }) - + it('should work with file created with `ember serve` (should append livereload pointing at serve instance)', () => { expect.assertions(1); - + const fileContent = fs.readFileSync(path.resolve(__dirname, 'fixtures', 'serve.html'), 'utf8'); - + expect(generatePreviewHead(parse(fileContent))).toMatchSnapshot(); }); }); + + describe('@findEnvironment', () => { + it('should return the meta node with name config/environment (which has the app env encoded as its content)', () => { + expect.assertions(1); + + const fileContent = fs.readFileSync(path.resolve(__dirname, 'fixtures', 'serve.html'), 'utf8'); + const config = parse(fileContent, true); + const env = findEnvironment(config); + + expect(env.name).toBe('storybook-ember-3-1/config/environment'); + }); + + it('should be robust against a file with no environment node', () => { + expect.assertions(1); + + const fileContent = fs.readFileSync(path.resolve(__dirname, 'fixtures', 'no-env.html'), 'utf8'); + const config = parse(fileContent, true); + const env = findEnvironment(config); + + expect(env).toBeUndefined(); + }); + }); + + describe('@extendEnvironment', () => { + it('deeply merges the original env object with override objects', () => { + const env = { + one: 'fish', + two: { + fishes: [ 'red', 'blue' ] + } + }; + + expect( + extendEnvironment( + env, + { two: { fishes: [ 'yellow', 'purple' ], updated: true } }, + { hello: 'world' } + ) + ).toEqual({ + one: 'fish', + two: { + fishes: [ 'yellow', 'purple' ], + updated: true, + }, + hello: 'world', + }); + }); + + it('should be robust against undefined input', () => { + const env = { + one: 'fish', + two: { + fishes: [ 'red', 'blue' ] + } + }; + + expect(extendEnvironment(env, undefined, undefined)).toEqual({ + one: 'fish', + two: { + fishes: [ 'red', 'blue' ] + } + }); + }); + }); + + describe('@overrideEnvironment', () => { + // Take a parsed fixture, get env node, override, assert strings as well as intermediates + // Assert works fine with undefined argument + it('returns a properly encoded meta value for an environment with overrides', () => { + expect.assertions(1); + + const fileContent = fs.readFileSync(path.resolve(__dirname, 'fixtures', 'serve.html'), 'utf8'); + const config = parse(fileContent, true); + const env = findEnvironment(config); + const overridden = overrideEnvironment(env, { + rootURL: '/foobar/', + meta: 'data', + APP: { + name: 'new-name-from-test', + }, + }); + + const envObject = JSON.parse(decodeURIComponent(env.content)); + envObject.rootURL = '/foobar/'; + envObject.meta = 'data'; + envObject.APP.name = 'new-name-from-test'; + + expect(overridden).toBe(encodeURIComponent(JSON.stringify(envObject))); + }); + }); });