diff --git a/.circleci/config.yml b/.circleci/config.yml index df16198..fff8d14 100755 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,88 +1,129 @@ -version: 2 +version: 2.1 -defaults: &defaults - working_directory: ~/project - docker: - - image: circleci/node:latest - environment: - NODE_ENV: test +executors: + node: + parameters: + browsers: + type: boolean + default: false + docker: + - image: circleci/node:latest<<# parameters.browsers >>-browsers< parameters.browsers >> + working_directory: ~/project + environment: + NODE_ENV: test + +commands: + attach-project: + steps: + - checkout + - attach_workspace: + at: ~/project jobs: setup: - <<: *defaults + executor: node steps: - # Checkout repository - checkout - - # Restore cache - restore_cache: key: yarn-{{ checksum "yarn.lock" }} - - # Install dependencies - run: name: Install Dependencies command: NODE_ENV=dev yarn - - # Keep cache - save_cache: key: yarn-{{ checksum "yarn.lock" }} paths: - "node_modules" - - # Persist workspace - persist_to_workspace: root: ~/project paths: - node_modules lint: - <<: *defaults + executor: node steps: - - checkout - - attach_workspace: - at: ~/project + - attach-project - run: name: Lint command: yarn lint audit: - <<: *defaults + executor: node steps: - - checkout - - attach_workspace: - at: ~/project + - attach-project - run: name: Security Audit command: yarn audit test-unit: - <<: *defaults + executor: node steps: - - checkout - - attach_workspace: - at: ~/project + - attach-project - run: name: Unit Tests command: yarn test:unit --coverage && yarn coverage - test-e2e: - docker: - - image: circleci/node:latest-browsers + test-e2e-ssr: + executor: node steps: - - checkout - - attach_workspace: - at: ~/project + - attach-project - run: - name: E2E Tests - command: yarn test:e2e + name: E2E SSR Tests + command: yarn test:e2e-ssr + - persist_to_workspace: + root: ~/project + paths: + - test/fixtures + + test-e2e-browser: + parameters: + browserString: + type: string + executor: + name: node + browsers: true + steps: + - attach-project + - run: + name: E2E Browser Tests + command: yarn test:e2e-browser + environment: + BROWSER_STRING: << parameters.browserString >> workflows: - version: 2 + version : 2 commit: jobs: - setup - - lint: { requires: [setup] } - - audit: { requires: [setup] } - - test-unit: { requires: [lint] } - - test-e2e: { requires: [lint] } + - lint: { requires: [setup] } + - audit: { requires: [setup] } + - test-unit: { requires: [lint] } + - test-e2e-ssr: { requires: [lint] } + - test-e2e-browser: + name: test-e2e-firefox + browserString: firefox/headless + requires: [test-e2e-ssr] + - test-e2e-browser: + name: test-e2e-chrome + browserString: chrome/selenium + requires: [test-e2e-ssr] + - test-e2e-browser: + name: test-e2e-ie + browserString: browserstack/local/windows 7/ie:9 + requires: [test-e2e-ssr] + filters: + branches: { ignore: /^pull\/.*/ } + - test-e2e-browser: + name: test-e2e-edge + browserString: browserstack/local/edge:15 + requires: [test-e2e-ssr] + filters: + branches: { ignore: /^pull\/.*/ } + - test-e2e-browser: + name: test-e2e-safari + browserString: browserstack/local/os x=snow leopard/safari:5.1 + requires: [test-e2e-ssr] + filters: + branches: { ignore: /^pull\/.*/ } + + diff --git a/.gitignore b/.gitignore index 1ae8e09..8a3d346 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Logs logs *.log +*.err npm-debug.log* # Runtime data @@ -44,3 +45,6 @@ es # examples yarn lock examples/yarn.lock + +# env vars +.env* diff --git a/.babelrc b/babel.config.js similarity index 71% rename from .babelrc rename to babel.config.js index 98d9142..b1fdd8e 100644 --- a/.babelrc +++ b/babel.config.js @@ -1,11 +1,13 @@ -{ +module.exports = { "plugins": ["@babel/plugin-syntax-dynamic-import"], "env": { "test": { "plugins": ["dynamic-import-node"], "presets": [ [ "@babel/env", { - "targets": { "node": "current" } + targets: { + node: "current" + } }] ] } diff --git a/package.json b/package.json index 9218268..bf0c6f3 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,9 @@ "prerelease": "git checkout master && git pull -r", "release": "yarn lint && yarn test && yarn build && standard-version", "postrelease": "git push origin master --follow-tags && yarn publish", - "test": "yarn test:unit && yarn test:e2e", - "test:e2e": "jest test/e2e", + "test": "yarn test:unit && yarn test:e2e-ssr && yarn test:e2e-browser", + "test:e2e-ssr": "jest test/e2e/ssr", + "test:e2e-browser": "jest test/e2e/browser", "test:unit": "jest test/unit" }, "dependencies": { @@ -66,51 +67,58 @@ }, "devDependencies": { "@babel/cli": "^7.2.3", - "@babel/core": "^7.3.4", + "@babel/core": "^7.4.0", "@babel/node": "^7.2.2", "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/preset-env": "^7.3.4", - "@nuxt/babel-preset-app": "^2.4.5", + "@babel/preset-env": "^7.4.2", + "@nuxt/babel-preset-app": "^2.5.0", "@nuxtjs/eslint-config": "^0.0.1", "@vue/server-test-utils": "^1.0.0-beta.29", "@vue/test-utils": "^1.0.0-beta.29", "babel-core": "^7.0.0-bridge", "babel-eslint": "^10.0.1", - "babel-jest": "^24.4.0", + "babel-jest": "^24.5.0", "babel-loader": "^8.0.5", "babel-plugin-dynamic-import-node": "^2.2.0", + "browserstack-local": "^1.3.7", + "chromedriver": "^2.46.0", "codecov": "^3.2.0", - "eslint": "^5.15.1", + "eslint": "^5.15.3", "eslint-config-standard": "^12.0.0", "eslint-plugin-import": "^2.16.0", - "eslint-plugin-jest": "^22.3.0", + "eslint-plugin-jest": "^22.4.1", "eslint-plugin-node": "^8.0.1", "eslint-plugin-promise": "^4.0.1", "eslint-plugin-standard": "^4.0.0", "eslint-plugin-vue": "^5.2.2", + "esm": "^3.2.18", "fs-extra": "^7.0.1", + "geckodriver": "^1.16.0", "is-wsl": "^1.1.0", - "jest": "^24.4.0", - "jest-environment-jsdom": "^24.4.0", - "jest-environment-jsdom-global": "^1.1.1", + "jest": "^24.5.0", + "jest-environment-jsdom": "^24.5.0", + "jest-environment-jsdom-global": "^1.2.0", "jsdom": "^14.0.0", "lodash": "^4.17.11", + "node-env-file": "^0.1.8", "puppeteer-core": "^1.13.0", "rimraf": "^2.6.3", - "rollup": "^1.6.0", + "rollup": "^1.7.0", "rollup-plugin-buble": "^0.19.6", "rollup-plugin-commonjs": "^9.2.1", - "rollup-plugin-json": "^3.1.0", + "rollup-plugin-json": "^4.0.0", "rollup-plugin-node-resolve": "^4.0.1", - "rollup-plugin-replace": "^2.1.0", + "rollup-plugin-replace": "^2.1.1", "rollup-plugin-terser": "^4.0.4", - "standard-version": "^5.0.1", - "vue": "^2.6.8", + "selenium-webdriver": "^4.0.0-alpha.1", + "standard-version": "^5.0.2", + "tib": "^0.4.0", + "vue": "^2.6.10", "vue-jest": "^3.0.4", "vue-loader": "^15.7.0", "vue-router": "^3.0.2", - "vue-server-renderer": "^2.6.8", - "vue-template-compiler": "^2.6.8", + "vue-server-renderer": "^2.6.10", + "vue-template-compiler": "^2.6.10", "vuepress": "^0.14.10", "vuepress-theme-vue": "^1.1.0", "webpack": "^4.29.6" diff --git a/src/shared/mixin.js b/src/shared/mixin.js index 362cc7b..732234e 100644 --- a/src/shared/mixin.js +++ b/src/shared/mixin.js @@ -47,7 +47,7 @@ export default function createMixin(Vue, options) { // coerce function-style metaInfo to a computed prop so we can observe // it on creation if (isFunction(this.$options[options.keyName])) { - if (isUndefined(this.$options.computed)) { + if (!this.$options.computed) { this.$options.computed = {} } this.$options.computed.$metaInfo = this.$options[options.keyName] @@ -82,7 +82,7 @@ export default function createMixin(Vue, options) { } }) - // add the navigation guards if they havent been added yet + // add the navigation guards if requested if (options.refreshOnceOnNavigation) { addNavGuards(this) } @@ -94,7 +94,10 @@ export default function createMixin(Vue, options) { // add the navigation guards if they havent been added yet // if metaInfo is defined as a function, this does call the computed fn redundantly // but as Vue internally caches the results of computed props it shouldnt hurt performance - if (!options.refreshOnceOnNavigation && this.$options[options.keyName].afterNavigation) { + if (!options.refreshOnceOnNavigation && ( + (this.$options[options.keyName] && this.$options[options.keyName].afterNavigation) || + (this.$options.computed && this.$options.computed.$metaInfo && (this.$options.computed.$metaInfo() || {}).afterNavigation) + )) { addNavGuards(this) } diff --git a/test/e2e/browser.test.js b/test/e2e/browser.test.js index 460a89e..ab40863 100644 --- a/test/e2e/browser.test.js +++ b/test/e2e/browser.test.js @@ -1,79 +1,116 @@ -import Browser from '../utils/browser' -import { buildFixture } from '../utils/build' +/** + * @jest-environment node + */ +import fs from 'fs' +import path from 'path' +import env from 'node-env-file' +import { browser as startBrowser } from 'tib' -const browser = new Browser() +const browserString = process.env.BROWSER_STRING || 'puppeteer/core' -describe('basic browser with ssr page', () => { - let page = null - let url - let html +describe(browserString, () => { + let browser + let page + const folder = path.resolve(__dirname, '..', 'fixtures/basic/.vue-meta/') beforeAll(async () => { - const fixture = await buildFixture('basic') - url = fixture.url - html = fixture.html + if (browserString.includes('browserstack') && browserString.includes('local')) { + const envFile = path.resolve(__dirname, '..', '..', '.env-browserstack') + if (fs.existsSync(envFile)) { + env(envFile) + } + } - await browser.start({ - // slowMo: 50, - // headless: false + browser = await startBrowser(browserString, { + BrowserStackLocal: { folder }, + extendPage(page) { + return { + async navigate(path) { + // IMPORTANT: use (arrow) function with block'ed body + // see: https://github.com/tunnckoCoreLabs/parse-function/issues/179 + await page.runAsyncScript((path) => { + return new Promise((resolve) => { + const oldTitle = document.title + + // local firefox has sometimes not updated the title + // even when the DOM is supposed to be fully updated + const waitTitleChanged = function () { + setTimeout(function () { + if (oldTitle !== document.title) { + resolve() + } else { + waitTitleChanged() + } + }, 50) + } + + window.$vueMeta.$once('routeChanged', waitTitleChanged) + window.$vueMeta.$router.push(path) + }) + }, path) + }, + routeData() { + return page.runScript(() => ({ + path: window.$vueMeta.$route.path, + query: window.$vueMeta.$route.query + })) + } + } + } }) }) - // Stop browser afterAll(async () => { - if (page) await page.close() - await browser.close() + if (browser) { + await browser.close() + } }) - test('validate ssr', () => { - const htmlTag = html.match(/]+)>/)[0] - expect(htmlTag).toContain('data-vue-meta-server-rendered') - expect(htmlTag).toContain(' lang="en" ') - expect(htmlTag).toContain(' amp ') - expect(htmlTag).not.toContain('allowfullscreen') - expect(html.match(/