diff --git a/.eslintrc.js b/.eslintrc.js index 382f20680fd..9fa379f73aa 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -56,7 +56,7 @@ module.exports = { '.mocharc.js', '.eslintrc.js', '.prettierrc.js', - 'bin/*', + 'bin/**', 'packages/private-build-infra/src/**/*.js', 'packages/unpublished-relationship-performance-test-app/bin/*.js', 'packages/unpublished-test-infra/src/**/*.js', @@ -109,12 +109,13 @@ module.exports = { // bin files { - files: ['bin/*', 'packages/unpublished-relationship-performance-test-app/bin/*'], + files: ['bin/**', 'packages/unpublished-relationship-performance-test-app/bin/*'], // eslint-disable-next-line node/no-unpublished-require rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { 'no-console': 'off', 'no-process-exit': 'off', 'node/no-unpublished-require': 'off', + 'node/no-unsupported-features/node-builtins': 'off', }), }, ], diff --git a/.github/workflows/asset-size-check.yml b/.github/workflows/asset-size-check.yml new file mode 100644 index 00000000000..a87395a8e7e --- /dev/null +++ b/.github/workflows/asset-size-check.yml @@ -0,0 +1,90 @@ +name: AssetSizeCheck + +on: + pull_request: + branches: + - master + +jobs: + asset-size-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: actions/setup-node@v1 + with: + node-version: 12.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Check SHA + run: | + sha=$(git rev-parse --short=8 HEAD) + echo "HEAD sha=$sha" + echo "GITHUB_SHA sha=$GITHUB_SHA" + mkdir -p tmp + echo $sha > tmp/sha-for-check.txt + originSha=$(git rev-parse HEAD^2) + echo $originSha > tmp/sha-for-commit.txt + git show --format=short --no-patch $originSha + - name: Checkout master + run: git checkout master + - name: Install dependencies for master + run: yarn install + - name: Build Production master + # This will leave the assets in a dist that is maintained when + # We switch back to the primary branch + run: yarn workspace ember-data ember build -e production + - name: Checkout ${{github.ref}} + # We checkout the PR Branch before parsing the master vendor file + # So that we are always using the current analysis tooling + run: | + sha=$(cat tmp/sha-for-check.txt) + git checkout --progress --force $sha + - name: Install dependencies for ${{github.ref}} + run: yarn install + - name: Parse Master Assets + run: | + node ./bin/asset-size-tracking/generate-analysis.js + mkdir -p tmp + mkdir -p tmp/asset-sizes + node ./bin/asset-size-tracking/print-analysis.js -show > tmp/asset-sizes/master-analysis.txt + - name: Upload Master Dist Artifacts + uses: actions/upload-artifact@v1 + with: + name: master-dist + path: packages/-ember-data/dist/assets + - name: Build Production ${{github.ref}} + run: yarn workspace ember-data ember build -e production + - name: Test Asset Sizes + run: | + mkdir -p tmp + mkdir -p tmp/asset-sizes + set -o pipefail + node ./bin/asset-size-tracking/generate-diff.js | tee tmp/asset-sizes/diff.txt + - name: Prepare Data For Report + if: failure() || success() + run: | + node ./bin/asset-size-tracking/generate-analysis.js + - name: Print Asset Size Report + if: failure() || success() + run: | + set -o pipefail + node ./bin/asset-size-tracking/print-analysis.js | tee tmp/asset-sizes/commit-analysis.txt + - name: Upload ${{github.ref}} Dist Artifacts + uses: actions/upload-artifact@v1 + with: + name: commit-dist + path: packages/-ember-data/dist/assets + - name: Upload Report Artifacts + uses: actions/upload-artifact@v1 + with: + name: reports + path: tmp/asset-sizes + - name: Report Asset Sizes + if: failure() || success() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + COMMENT_MARKER="Asset Size Report for "= + sha=$(cat tmp/sha-for-commit.txt) + node ./bin/asset-size-tracking/src/create-comment-text.js $sha > tmp/asset-sizes/comment.txt + COMMENT_TEXT="@./tmp/asset-sizes/comment.txt" + source bin/asset-size-tracking/src/post-comment.sh diff --git a/.gitignore b/.gitignore index 71ac20c959c..a2bcc724cb9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ tmp # dependencies bower_components node_modules +bin/asset-size-tracking/current-data.json # misc .env* diff --git a/bin/asset-size-tracking/generate-analysis.js b/bin/asset-size-tracking/generate-analysis.js new file mode 100644 index 00000000000..f0855a33ec4 --- /dev/null +++ b/bin/asset-size-tracking/generate-analysis.js @@ -0,0 +1,20 @@ +'use strict'; +/** + * Analyze Ember-Data Modules + * + * Generates a JSON file with details of size costs of each individual module + * and package. You should crate a production build of the ember-data + * package prior to running this script. + * + */ +const fs = require('fs'); +const path = require('path'); +let INPUT_FILE = process.argv[2] || false; +const parseModules = require('./src/parse-modules'); +const getBuiltDist = require('./src/get-built-dist'); + +const builtAsset = getBuiltDist(INPUT_FILE); +const library = parseModules(builtAsset); +const outputPath = path.resolve(__dirname, './current-data.json'); + +fs.writeFileSync(outputPath, JSON.stringify(library, null, 2)); diff --git a/bin/asset-size-tracking/generate-diff.js b/bin/asset-size-tracking/generate-diff.js new file mode 100644 index 00000000000..c3d5a9adeec --- /dev/null +++ b/bin/asset-size-tracking/generate-diff.js @@ -0,0 +1,246 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const Library = require('./src/library'); +const parseModules = require('./src/parse-modules'); +const getBuiltDist = require('./src/get-built-dist'); +const chalk = require('chalk'); +const library_failure_threshold = 15; +const package_warn_threshold = 0; + +let BASE_DATA_FILE = process.argv[2] || false; +let NEW_VENDOR_FILE = process.argv[3] || false; + +if (!BASE_DATA_FILE) { + BASE_DATA_FILE = path.resolve(__dirname, './current-data.json'); +} + +const data = fs.readFileSync(BASE_DATA_FILE, 'utf-8'); +const current_library = Library.fromData(JSON.parse(data)); + +const builtAsset = getBuiltDist(NEW_VENDOR_FILE); +const new_library = parseModules(builtAsset); + +function getDiff(oldLibrary, newLibrary) { + const diff = { + name: oldLibrary.name, + currentSize: oldLibrary.absoluteSize, + newSize: newLibrary.absoluteSize, + currentSizeCompressed: oldLibrary.compressedSize, + newSizeCompressed: newLibrary.compressedSize, + packages: {}, + }; + oldLibrary.packages.forEach(pkg => { + diff.packages[pkg.name] = { + name: pkg.name, + currentSize: pkg.absoluteSize, + newSize: 0, + currentSizeCompressed: pkg.compressedSize, + newSizeCompressed: 0, + modules: {}, + }; + let modules = diff.packages[pkg.name].modules; + pkg.modules.forEach(m => { + modules[m.name] = { + name: m.name, + currentSize: m.absoluteSize, + newSize: 0, + currentSizeCompressed: m.compressedSize, + newSizeCompressed: 0, + }; + }); + }); + newLibrary.packages.forEach(pkg => { + diff.packages[pkg.name] = diff.packages[pkg.name] || { + name: pkg.name, + currentSize: 0, + newSize: pkg.absoluteSize, + currentSizeCompressed: 0, + newSizeCompressed: pkg.compressedSize, + modules: {}, + }; + diff.packages[pkg.name].newSize = pkg.absoluteSize; + diff.packages[pkg.name].newSizeCompressed = pkg.compressedSize; + let modules = diff.packages[pkg.name].modules; + pkg.modules.forEach(m => { + modules[m.name] = modules[m.name] || { + name: m.name, + currentSize: 0, + newSize: m.absoluteSize, + currentSizeCompressed: 0, + newSizeCompressed: m.compressedSize, + }; + modules[m.name].newSize = m.absoluteSize; + modules[m.name].newSizeCompressed = m.compressedSize; + }); + }); + diff.packages = Object.values(diff.packages); + diff.packages.forEach(pkg => { + pkg.modules = Object.values(pkg.modules); + }); + + return diff; +} + +const diff = getDiff(current_library, new_library); + +function analyzeDiff(diff) { + let failures = []; + let warnings = []; + + if (diff.currentSize < diff.newSize) { + let delta = diff.newSize - diff.currentSize; + let compressedDelta = diff.newSizeCompressed - diff.currentSizeCompressed; + if (delta > library_failure_threshold && compressedDelta > 0) { + failures.push( + `The size of the library ${diff.name} has increased by ${formatBytes(delta)} (${formatBytes( + compressedDelta + )} compressed) which exceeds the failure threshold of ${library_failure_threshold} bytes.` + ); + } + } + + diff.packages.forEach(pkg => { + if (pkg.currentSize < pkg.newSize) { + let delta = pkg.newSize - pkg.currentSize; + if (delta > package_warn_threshold) { + warnings.push(`The uncompressed size of the package ${pkg.name} has increased by ${formatBytes(delta)}.`); + } + } + }); + + return { failures, warnings }; +} + +function printDiff(diff) { + console.log('\n```\n'); + printItem(diff); + diff.packages.forEach(pkg => { + printItem(pkg, 2); + pkg.modules.forEach(m => { + printItem(m, 4); + }); + }); + console.log('\n```\n'); +} + +function printItem(item, indent = 0) { + if (item.currentSize !== item.newSize) { + const indentColor = indent >= 4 ? 'grey' : indent >= 2 ? 'yellow' : indent >= 0 ? 'magenta' : 'green'; + console.log( + leftPad(chalk[indentColor](item.name) + ' ' + formatSize(item, false) + ' ' + formatSize(item, true), indent * 2) + ); + } +} + +function formatSize(item, isCompressed = false) { + let size = formatBytes(isCompressed ? item.newSizeCompressed : item.newSize); + let delta = formatDelta(item, isCompressed); + + return isCompressed ? chalk.grey(`(${chalk.white(size)} ${delta} compressed)`) : `${chalk.white(size)} ${delta}`; +} + +function formatDelta(item, isCompressed = false) { + let delta = isCompressed ? item.newSizeCompressed - item.currentSizeCompressed : item.newSize - item.currentSize; + + if (delta === 0) { + return chalk.black('±0 B'); + } + + if (delta < 0) { + return chalk.green(`${formatBytes(delta)}`); + } else { + return chalk.red(`+${formatBytes(delta)}`); + } +} + +function humanizeNumber(n) { + let s = n.toFixed(2); + if (s.charAt(s.length - 1) === '0') { + s = n.toFixed(1); + + if (s.charAt(s.length - 2) === '0') { + s = n.toFixed(0); + } + } + return s; +} + +function formatBytes(b) { + let str; + if (b > 1024 || b < -1024) { + str = humanizeNumber(b / 1024) + ' KB'; + } else { + str = humanizeNumber(b) + ' B'; + } + + return str; +} + +function leftPad(str, len, char = ' ') { + for (let i = 0; i < len; i++) { + str = char + str; + } + return str; +} + +const { failures, warnings } = analyzeDiff(diff); + +if (failures.length) { + console.log(`\n
\n ${failures[0]}`); + if (failures.length > 1) { + console.log('\nFailed Checks\n-----------------------'); + failures.forEach(f => { + console.log(f); + }); + } + if (warnings.length) { + console.log('\nWarnings\n-----------------------'); + warnings.forEach(w => { + console.log(w); + }); + } + console.log('\n**Changeset**\n'); + printDiff(diff); + console.log('\n
'); + process.exit(1); +} else { + let delta = diff.currentSize - diff.newSize; + if (delta === 0) { + console.log(`\n
\n ${diff.name} has not changed in size`); + } else if (delta > 0) { + console.log( + `\n
\n ${diff.name} shrank by ${formatBytes(delta)} (${formatBytes( + diff.currentSizeCompressed - diff.newSizeCompressed + )} compressed)` + ); + } else { + let compressedDelta = diff.newSizeCompressed - diff.currentSizeCompressed; + if (-1 * delta < library_failure_threshold) { + console.log( + `\n
\n ${diff.name} increased by ${formatBytes(-1 * delta)} (${formatBytes( + compressedDelta + )} compressed) which is within the allowed tolerance of ${library_failure_threshold} bytes uncompressed` + ); + } else { + console.log( + `\n
\n ${diff.name} increased by ${formatBytes( + -1 * delta + )} uncompressed but decreased by ${formatBytes(-1 * compressedDelta)} compressed` + ); + } + } + if (warnings.length) { + console.log('\nWarnings\n-----------------------'); + warnings.forEach(w => { + console.log(w); + }); + } else { + console.log('\nIf any packages had changed sizes they would be listed here.'); + } + console.log('\n**Changeset**\n'); + printDiff(diff); + console.log('\n
'); + process.exit(0); +} diff --git a/bin/asset-size-tracking/print-analysis.js b/bin/asset-size-tracking/print-analysis.js new file mode 100644 index 00000000000..4a533007598 --- /dev/null +++ b/bin/asset-size-tracking/print-analysis.js @@ -0,0 +1,17 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const Library = require('./src/library'); + +let INPUT_FILE = process.argv[2] !== '-show' ? process.argv[2] : false; +let SHOW_MODULES = process.argv[2] === '-show' || process.argv[3] === '-show'; + +if (!INPUT_FILE) { + INPUT_FILE = path.resolve(__dirname, './current-data.json'); +} + +const data = fs.readFileSync(INPUT_FILE, 'utf-8'); + +const library = Library.fromData(JSON.parse(data)); +library.print(SHOW_MODULES); diff --git a/bin/asset-size-tracking/src/create-comment-text.js b/bin/asset-size-tracking/src/create-comment-text.js new file mode 100644 index 00000000000..9d706f3e82e --- /dev/null +++ b/bin/asset-size-tracking/src/create-comment-text.js @@ -0,0 +1,15 @@ +const fs = require('fs'); +const path = require('path'); +const GITHUB_SHA = process.argv[2]; + +const diffPath = path.resolve(__dirname, '../../../tmp/asset-sizes/diff.txt'); +const analysisPath = path.resolve(__dirname, '../../../tmp/asset-sizes/commit-analysis.txt'); +const diffText = fs.readFileSync(diffPath); +const analysisText = fs.readFileSync(analysisPath); + +const commentText = `Asset Size Report for ${GITHUB_SHA}\n${diffText}\n
\n Full Asset Analysis\n\n\`\`\`${analysisText}\n\`\`\`\n
`; +const commentJSON = { + body: commentText, +}; + +console.log(JSON.stringify(commentJSON, null, 2)); diff --git a/bin/asset-size-tracking/src/get-built-dist.js b/bin/asset-size-tracking/src/get-built-dist.js new file mode 100644 index 00000000000..2370d89b6dc --- /dev/null +++ b/bin/asset-size-tracking/src/get-built-dist.js @@ -0,0 +1,37 @@ +'use strict'; +/** + * Analyze Ember-Data Modules + * + * Generates a JSON file with details of size costs of each individual module + * and package. You should crate a production build of the ember-data + * package prior to running this script. + * + */ +const fs = require('fs'); +const path = require('path'); + +module.exports = function getDistFile(INPUT_FILE) { + if (!INPUT_FILE) { + try { + let dirPath = path.resolve(__dirname, '../../../packages/-ember-data/dist/assets'); + let dir = fs.readdirSync(dirPath, 'utf-8'); + + for (let i = 0; i < dir.length; i++) { + let name = dir[i]; + if (name.indexOf('vendor') !== -1 && name.indexOf('.js') !== -1) { + INPUT_FILE = path.resolve(dirPath, name); + } + } + } catch (e) { + console.log(`No vendor.js file found to process: ${e.message}`); + process.exit(1); + } + + if (!INPUT_FILE) { + console.log('No vendor.js file found to process'); + process.exit(1); + } + } + + return fs.readFileSync(INPUT_FILE, 'utf-8'); +}; diff --git a/bin/asset-size-tracking/src/library.js b/bin/asset-size-tracking/src/library.js new file mode 100644 index 00000000000..83b76e443d3 --- /dev/null +++ b/bin/asset-size-tracking/src/library.js @@ -0,0 +1,237 @@ +const zlib = require('zlib'); +const TablePads = { + name: 45, + bytes: 9, + compressedBytes: 10, + percentOfPackage: 13, +}; +const BROTLI_OPTIONS = { + params: { + [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT, + }, +}; + +function getCompressedSize(code) { + return Buffer.byteLength(zlib.brotliCompressSync(code, BROTLI_OPTIONS)); +} + +class Library { + constructor(name) { + this.name = name; + this.packages = []; + this._packageMap = {}; + this._concatModule = ''; + this._compressedSize = null; + } + getPackage(name) { + let pkg = this._packageMap[name]; + + if (!pkg) { + pkg = this._packageMap[name] = new Package(name, this); + this.packages.push(pkg); + } + + return pkg; + } + get concatModule() { + return this._concatModule; + } + get absoluteSize() { + return byteCount(this.concatModule); + } + get compressedSize() { + return this._compressedSize || (this._compressedSize = getCompressedSize(this.concatModule)); + } + sort() { + this.packages = this.packages.sort((a, b) => { + return a.compressedSize > b.compressedSize ? -1 : 1; + }); + this.packages.forEach(p => p.sort()); + } + print(showModules) { + console.log('\n\nAsset Size Report'); + console.log('=================\n\n'); + + console.log(`Library: ${this.name}`); + console.table({ + bytes: formatBytes(this.absoluteSize), + compressed: formatBytes(this.compressedSize), + packages: this.packages.length, + modules: this.packages.reduce((v, c) => v + c.modules.length, 0), + }); + this.packages.forEach(p => p.print()); + if (showModules) { + this.packages.forEach(p => { + p.modules.forEach(m => { + console.log('\n\n'); + console.log(m.code); + }); + }); + } + } + toJSON() { + return { + name: this.name, + packages: this.packages, + }; + } + + static fromData(data) { + const library = new Library(data.name); + data.packages.forEach(p => { + const pkg = library.getPackage(p.name); + p.modules.forEach(m => { + pkg.addModule(m.name, m.code); + }); + }); + return library; + } +} + +class Package { + constructor(name, library) { + this.name = name; + this.library = library; + this.modules = []; + this._concatModule = ''; + } + addModule(name, code) { + let mod = new Module(name, this, code); + this.modules.push(mod); + this._concatModule += code; + this.library._concatModule += code; + return mod; + } + get concatModule() { + return this._concatModule; + } + get absoluteSize() { + return byteCount(this.concatModule); + } + get compressedSize() { + return ( + Math.floor((this.concatModule.length / this.library.concatModule.length) * this.library.compressedSize * 100) / + 100 + ); + } + get percentOfLibrary() { + return getRelativeSizeOf(this.library, this); + } + sort() { + this.modules = this.modules.sort((a, b) => { + return a.compressedSize > b.compressedSize ? -1 : 1; + }); + } + print() { + console.log('\nPackage: ' + this.name); + console.table({ + bytes: formatBytes(this.absoluteSize), + compressed: formatBytes(this.compressedSize), + '% Of Library': this.percentOfLibrary, + }); + console.log( + `\t${rightPad('Module', TablePads.name)} | ` + + `${rightPad('Bytes', TablePads.bytes)} | ` + + `${rightPad('Compressed', TablePads.compressedBytes)} | ` + + `${rightPad('% of Package', TablePads.percentOfPackage)} | ` + + `% Of Library` + ); + console.log( + '\t-----------------------------------------------------------------------------------------------------' + ); + this.modules.forEach(s => s.print()); + } + toJSON() { + return { + name: this.name, + modules: this.modules, + }; + } +} + +class Module { + constructor(name, pkg, code) { + this.name = name; + this.package = pkg; + this.code = code; + } + get size() { + return this.code.length; + } + get absoluteSize() { + return byteCount(this.code); + } + get compressedSize() { + return ( + Math.floor((this.size / this.package.library.concatModule.length) * this.package.library.compressedSize * 100) / + 100 + ); + } + get bytes() { + return formatBytes(this.absoluteSize); + } + get compressedBytes() { + return formatBytes(this.compressedSize); + } + get percentOfPackage() { + return getRelativeSizeOf(this.package, this); + } + get percentOfLibrary() { + return getRelativeSizeOf(this.package.library, this); + } + print() { + console.log( + '\t' + + rightPad(this.name, TablePads.name) + + ' | ' + + rightPad(this.bytes, TablePads.bytes) + + ' | ' + + rightPad(this.compressedBytes, TablePads.compressedBytes) + + ' | ' + + rightPad(this.percentOfPackage, TablePads.percentOfPackage) + + ' | ' + + this.percentOfLibrary + ); + } + toJSON() { + return { + name: this.name, + code: this.code, + }; + } +} + +function rightPad(str, len, char = ' ') { + while (str.length < len) { + str += char; + } + return str; +} + +function getRelativeSizeOf(base, component) { + const { absoluteSize } = component; + const { absoluteSize: totalSize } = base; + + return `${per(absoluteSize, totalSize)}`; +} + +function per(size, total) { + return ((size / total) * 100).toFixed(1); +} + +function formatBytes(b) { + let str; + if (b > 1024) { + str = (b / 1024).toFixed(2) + ' KB'; + } else { + str = b.toFixed(2) + ' B'; + } + + return str; +} + +function byteCount(s) { + return encodeURI(s).split(/%..|./).length - 1; +} + +module.exports = Library; diff --git a/bin/asset-size-tracking/src/parse-modules.js b/bin/asset-size-tracking/src/parse-modules.js new file mode 100644 index 00000000000..22273365c6d --- /dev/null +++ b/bin/asset-size-tracking/src/parse-modules.js @@ -0,0 +1,42 @@ +const Library = require('./library'); +const moduleNames = ['ember-data', '@ember-data', '@ember/ordered-set', 'ember-inflector']; + +module.exports = function parseModules(builtAsset) { + let modules = builtAsset + .split('define(') + .join('MODULE_SPLIT_POINTdefine(') + .split('MODULE_SPLIT_POINT'); + + modules = modules.filter(mod => { + for (let i = 0; i < moduleNames.length; i++) { + let projectName = moduleNames[i]; + if (mod.indexOf(projectName) === 8) { + return true; + } + } + return false; + }); + + let library = new Library('EmberData'); + + modules.forEach(m => { + let end = m.indexOf(',', 8) - 1; + let name = m.substring(8, end); + + let packageName = 'ember-data'; + + if (name.indexOf('@ember-data/') === 0) { + let subPackageEnd = name.indexOf('/', 12); + packageName = name.substring(0, subPackageEnd); + } else if (name.indexOf('ember-inflector') === 0) { + packageName = 'ember-inflector'; + } else if (name.indexOf('@ember/ordered-set') === 0) { + packageName = '@ember/ordered-set'; + } + + library.getPackage(packageName).addModule(name, m); + }); + + library.sort(); + return library; +}; diff --git a/bin/asset-size-tracking/src/post-comment.sh b/bin/asset-size-tracking/src/post-comment.sh new file mode 100755 index 00000000000..9e7635613c5 --- /dev/null +++ b/bin/asset-size-tracking/src/post-comment.sh @@ -0,0 +1,73 @@ + #!/bin/bash + +# Shamelessly stolen from the very awesome ShakingFingerAction +# Written by the amazing https://twitter.com/jessfraz +# https://github.com/jessfraz/shaking-finger-action +# Generified to enable this form of comment management +# for our CI Reports (AssetSize | Perf Analysis etc.) + +if [[ -z "$COMMENT_MARKER" ]]; then + echo "Set the COMMENT_MARKER env variable." + return 1 +fi + +if [[ -z "$COMMENT_TEXT" ]]; then + echo "Set the COMMENT_TEXT env variable." + return 1 +fi + +if [[ -z "$GITHUB_REPOSITORY" ]]; then + echo "Set the GITHUB_REPOSITORY env variable." + return 1 +fi + +# Github seems to be playin coy, getting access from +# a sourced script is extremely hard +if [[ -z "${GITHUB_TOKEN}" ]]; then + echo "Configure the env to include the GITHUB_TOKEN secret variable." + return 1 +fi + +URI=https://api.github.com +API_VERSION=v3 +API_HEADER="Accept: application/vnd.github.${API_VERSION}+json; application/vnd.github.antiope-preview+json" +AUTH_HEADER="Authorization: token ${GITHUB_TOKEN}" + +delete_comment_if_exists() { + # Get all the comments for the pull request. + body=$(curl -sSL -H "${AUTH_HEADER}" -H "${API_HEADER}" "${URI}/repos/${GITHUB_REPOSITORY}/issues/${NUMBER}/comments") + + for row in $(echo -E "${body}" | jq --raw-output '.[] | @base64'); do + comment=$(echo -E "${row}" | base64 --decode | jq --raw-output '{id: .id, body: .body, author: .user.login}') + id=$(echo -E "$comment" | jq -r '.id') + b=$(echo -E "$comment" | jq -r '.body') + + if [[ "$b" == *"${COMMENT_MARKER}"* ]]; then + # We have found our comment. + # Delete it. + + echo "Deleting old comment ID: $id" + DELETE_URL="${URI}/repos/${GITHUB_REPOSITORY}/issues/comments/${id}" + echo $DELETE_URL; + curl -sSL -H "${AUTH_HEADER}" -H "${API_HEADER}" -X DELETE $DELETE_URL + fi + done +} + +post_comment() { + curl -sSL -H "${AUTH_HEADER}" -H "${API_HEADER}" -d "$COMMENT_TEXT" -H "Content-Type: application/json" -X POST "${URI}/repos/${GITHUB_REPOSITORY}/issues/${NUMBER}/comments" +} + +main() { + # Validate the GitHub token. + curl -o /dev/null -sSL -H "${AUTH_HEADER}" -H "${API_HEADER}" "${URI}/repos/${GITHUB_REPOSITORY}" || { echo "Error: Invalid repo, token or network issue!"; exit 1; } + + # Get the pull request number. + NUMBER=$(jq --raw-output .number "$GITHUB_EVENT_PATH") + echo "running $GITHUB_ACTION for PR #${NUMBER}" + + delete_comment_if_exists; + post_comment; +} + +main diff --git a/bin/relationship-performance-check b/bin/relationship-performance-check deleted file mode 100755 index fb95b4b2077..00000000000 --- a/bin/relationship-performance-check +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -APP_PATH=packages/unpublished-relationship-performance-test-app -HAR_REMIX_SCRIPT="$APP_PATH/bin/har-remix.js" -WORKSPACE=relationship-performance-test-app -CONTROl_BRANCH=master -EXPERIMENT_BRANCH=$(git branch | sed -n -e 's/^\* \(.*\)/\1/p') - -git checkout $CONTROl_BRANCH - -if test -f $HAR_REMIX_SCRIPT; then - yarn install - yarn workspace $WORKSPACE ember build -e production --output-path dist-control - git checkout $EXPERIMENT_BRANCH - yarn install - yarn workspace $WORKSPACE ember build -e production --output-path dist-experiment - HR_PORT=4200 HR_GROUP=control pm2 start $HAR_REMIX_SCRIPT --name control - HR_PORT=4201 HR_GROUP=experiment pm2 start $HAR_REMIX_SCRIPT --name experiment - yarn workspace $WORKSPACE tracerbench:compare - pm2 kill -else - echo "har-remix.js does not exist on the $CONTROl_BRANCH branch" - git checkout $EXPERIMENT_BRANCH -fi \ No newline at end of file diff --git a/bin/relationship-performance-tracking/src/generate-analysis.sh b/bin/relationship-performance-tracking/src/generate-analysis.sh new file mode 100755 index 00000000000..222a450d6e9 --- /dev/null +++ b/bin/relationship-performance-tracking/src/generate-analysis.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +if [[ -z "$CONTROL_COMMIT" ]]; then + echo "Set the CONTROL_COMMIT env variable." + return 1 +fi + +if [[ -z "$EXPERIMENT_COMMIT" ]]; then + echo "Set the EXPERIMENT_COMMIT env variable." + return 1 +fi + +APP_PATH=packages/unpublished-relationship-performance-test-app +HAR_REMIX_SCRIPT="$APP_PATH/bin/har-remix.js" +WORKSPACE=relationship-performance-test-app + +git checkout $CONTROl_COMMIT +yarn install +yarn workspace $WORKSPACE ember build -e production --output-path dist-control +git checkout $EXPERIMENT_COMMIT +yarn install +yarn workspace $WORKSPACE ember build -e production --output-path dist-experiment +HR_PORT=4200 HR_GROUP=control pm2 start $HAR_REMIX_SCRIPT --name control +HR_PORT=4201 HR_GROUP=experiment pm2 start $HAR_REMIX_SCRIPT --name experiment +yarn workspace $WORKSPACE tracerbench:compare +pm2 kill diff --git a/bin/relationship-performance-tracking/src/head-vs-master.sh b/bin/relationship-performance-tracking/src/head-vs-master.sh new file mode 100755 index 00000000000..39c966d2d95 --- /dev/null +++ b/bin/relationship-performance-tracking/src/head-vs-master.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +CONTROL_COMMIT=$(git rev-parse master) +EXPERIMENT_COMMIT=$(git rev-parse HEAD) +./generate-analysis \ No newline at end of file diff --git a/package.json b/package.json index 6df60e358d2..a3d0cf0af38 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "test-external:ember-data-relationship-tracker": "./bin/test-external-partner-project.js ember-data-relationship-tracker https://github.com/ef4/ember-data-relationship-tracker.git" }, "devDependencies": { - "@babel/plugin-transform-typescript": "^7.6.3", + "zlib": "1.0.5", + "@babel/plugin-transform-typescript": "^7.7.0", "@ember/optional-features": "^1.1.0", "@types/ember": "^3.1.1", "@types/ember-qunit": "^3.4.7", @@ -48,8 +49,8 @@ "@types/ember__test-helpers": "~0.7.9", "@types/qunit": "^2.5.3", "@types/rsvp": "^4.0.3", - "@typescript-eslint/eslint-plugin": "^2.6.0", - "@typescript-eslint/parser": "^2.6.0", + "@typescript-eslint/eslint-plugin": "^2.6.1", + "@typescript-eslint/parser": "^2.6.1", "babel-eslint": "^10.0.3", "babel-plugin-debug-macros": "^0.3.3", "babel-plugin-feature-flags": "^0.3.1", @@ -80,7 +81,7 @@ "ember-cli-inject-live-reload": "^2.0.2", "ember-cli-internal-test-helpers": "^0.9.1", "ember-cli-path-utils": "^1.0.0", - "ember-cli-pretender": "^3.1.1", + "ember-cli-pretender": "^3.2.0", "ember-cli-shims": "^1.2.0", "ember-cli-sri": "^2.1.1", "ember-cli-string-utils": "^1.1.0", @@ -90,7 +91,7 @@ "ember-cli-typescript-blueprints": "^3.0.0", "ember-cli-uglify": "3.0.0", "ember-cli-version-checker": "^3.1.2", - "ember-cli-yuidoc": "^0.8.8", + "ember-cli-yuidoc": "^0.9.1", "ember-compatibility-helpers": "^1.2.0", "ember-decorators-polyfill": "^1.1.1", "ember-disable-prototype-extensions": "^1.1.3", diff --git a/packages/-ember-data/addon/-private/index.js b/packages/-ember-data/addon/-private/index.js index 959b1e2781a..330b7802ee7 100644 --- a/packages/-ember-data/addon/-private/index.js +++ b/packages/-ember-data/addon/-private/index.js @@ -1,8 +1,9 @@ // public -export { Errors, Snapshot } from '@ember-data/store/-private'; export { default as Store } from '@ember-data/store'; export { default as DS } from './core'; export { default as isEnabled } from './features'; +export { Errors } from '@ember-data/model/-private'; +export { Snapshot } from '@ember-data/store/-private'; // `ember-data-model-fragments` relies on `RootState` and `InternalModel` // `ember-data-model-fragments' and `ember-data-change-tracker` rely on `normalizeModelName` diff --git a/packages/-ember-data/package.json b/packages/-ember-data/package.json index abfcaa1f4b1..4a6131b4d8b 100644 --- a/packages/-ember-data/package.json +++ b/packages/-ember-data/package.json @@ -38,7 +38,7 @@ "ember-inflector": "^3.0.1" }, "devDependencies": { - "@babel/plugin-transform-typescript": "^7.6.3", + "@babel/plugin-transform-typescript": "^7.7.0", "@ember/optional-features": "^1.1.0", "@types/ember": "^3.1.1", "@types/ember-qunit": "^3.4.7", @@ -60,13 +60,13 @@ "ember-cli-htmlbars": "^4.0.8", "ember-cli-inject-live-reload": "^2.0.2", "ember-cli-internal-test-helpers": "^0.9.1", - "ember-cli-pretender": "^3.1.1", + "ember-cli-pretender": "^3.2.0", "ember-cli-shims": "^1.2.0", "ember-cli-sri": "^2.1.1", "ember-cli-test-loader": "^2.2.0", "ember-cli-typescript-blueprints": "^3.0.0", "ember-cli-uglify": "3.0.0", - "ember-cli-yuidoc": "^0.8.8", + "ember-cli-yuidoc": "^0.9.1", "ember-compatibility-helpers": "^1.2.0", "ember-decorators-polyfill": "^1.1.1", "ember-disable-prototype-extensions": "^1.1.3", diff --git a/packages/-ember-data/tests/helpers/deprecated-test.js b/packages/-ember-data/tests/helpers/deprecated-test.js index 25bf20ccd67..7ac5237676a 100644 --- a/packages/-ember-data/tests/helpers/deprecated-test.js +++ b/packages/-ember-data/tests/helpers/deprecated-test.js @@ -2,11 +2,6 @@ import { test } from 'qunit'; import VERSION from 'ember-data/version'; import { DEBUG } from '@glimmer/env'; -// temporary so that we can split out test fixes -// from landing this. If working locally turn this -// on to have tests fail that require being fixed. -export const SHOULD_ASSERT_ALL = false; - // small comparison function for major and minor semver values function gte(EDVersion, DeprecationVersion) { let _edv = EDVersion.split('.'); @@ -31,12 +26,10 @@ export function deprecatedTest(testName, deprecation, testCallback) { async function interceptor(assert) { await testCallback.call(this, assert); if (DEBUG) { - if (SHOULD_ASSERT_ALL) { - if (typeof assert.test.expected === 'number') { - assert.test.expected += 1; - } - assert.expectDeprecation(deprecation); + if (typeof assert.test.expected === 'number') { + assert.test.expected += 1; } + assert.expectDeprecation(deprecation); } } diff --git a/packages/-ember-data/tests/helpers/qunit-asserts/assert-assertion.ts b/packages/-ember-data/tests/helpers/qunit-asserts/assert-assertion.ts index b65acedaf85..389c91c60d8 100644 --- a/packages/-ember-data/tests/helpers/qunit-asserts/assert-assertion.ts +++ b/packages/-ember-data/tests/helpers/qunit-asserts/assert-assertion.ts @@ -51,17 +51,18 @@ export function configureAssertionHandler() { label?: string ): Promise { let outcome; - if (DEBUG) { - try { - let result = cb(); - if (isThenable(result)) { - await result; - } - outcome = verifyAssertion('', matcher, label); - } catch (e) { - outcome = verifyAssertion(e.message, matcher, label); + + try { + let result = cb(); + if (isThenable(result)) { + await result; } - } else { + outcome = verifyAssertion('', matcher, label); + } catch (e) { + outcome = verifyAssertion(e.message, matcher, label); + } + + if (!DEBUG) { outcome = { result: true, actual: '', @@ -85,6 +86,15 @@ export function configureAssertionHandler() { outcome = verifyNoAssertion(e.message, label); } + if (!DEBUG) { + outcome = { + result: true, + actual: '', + expected: '', + message: `Assertions do not run in production environments`, + }; + } + this.pushResult(outcome); }; } diff --git a/packages/-ember-data/tests/helpers/qunit-asserts/assert-deprecation.ts b/packages/-ember-data/tests/helpers/qunit-asserts/assert-deprecation.ts index 5f64ae46878..24f703b3c11 100644 --- a/packages/-ember-data/tests/helpers/qunit-asserts/assert-deprecation.ts +++ b/packages/-ember-data/tests/helpers/qunit-asserts/assert-deprecation.ts @@ -1,5 +1,6 @@ import QUnit from 'qunit'; import { registerDeprecationHandler } from '@ember/debug'; +import { DEBUG } from '@glimmer/env'; import { checkMatcher } from './check-matcher'; import isThenable from './utils/is-thenable'; @@ -157,6 +158,16 @@ export function configureDeprecationHandler() { } let result = verifyDeprecation(config, label); + + if (!DEBUG) { + result = { + result: true, + actual: { id: config.id, count: 0 }, + expected: { id: config.id, count: 0 }, + message: `Deprecations do not trigger in production environments`, + }; + } + this.pushResult(result); if (callback) { DEPRECATIONS_FOR_TEST = origDeprecations.concat(DEPRECATIONS_FOR_TEST); @@ -178,6 +189,16 @@ export function configureDeprecationHandler() { } let result = verifyNoDeprecation(filter, label); + + if (!DEBUG) { + result = { + result: true, + actual: [], + expected: [], + message: `Deprecations do not trigger in production environments`, + }; + } + this.pushResult(result); DEPRECATIONS_FOR_TEST = origDeprecations.concat(DEPRECATIONS_FOR_TEST); }; diff --git a/packages/-ember-data/tests/helpers/qunit-asserts/assert-warning.ts b/packages/-ember-data/tests/helpers/qunit-asserts/assert-warning.ts index 51f8022fa5d..70e224601ba 100644 --- a/packages/-ember-data/tests/helpers/qunit-asserts/assert-warning.ts +++ b/packages/-ember-data/tests/helpers/qunit-asserts/assert-warning.ts @@ -1,5 +1,6 @@ import QUnit from 'qunit'; import { registerWarnHandler } from '@ember/debug'; +import { DEBUG } from '@glimmer/env'; import { checkMatcher } from './check-matcher'; import isThenable from './utils/is-thenable'; @@ -148,6 +149,16 @@ export function configureWarningHandler() { } let result = verifyWarning(config, label); + + if (!DEBUG) { + result = { + result: true, + actual: { id: config.id, count: 0 }, + expected: { id: config.id, count: 0 }, + message: `Warnings do not trigger in production environments`, + }; + } + this.pushResult(result); WARNINGS_FOR_TEST = origWarnings.concat(WARNINGS_FOR_TEST); }; @@ -164,6 +175,16 @@ export function configureWarningHandler() { } let result = verifyNoWarning(label); + + if (!DEBUG) { + result = { + result: true, + actual: [], + expected: [], + message: `Warnings do not trigger in production environments`, + }; + } + this.pushResult(result); WARNINGS_FOR_TEST = origWarnings.concat(WARNINGS_FOR_TEST); }; diff --git a/packages/-ember-data/tests/integration/application-test.js b/packages/-ember-data/tests/integration/application-test.js index 8cc306f567c..d995b4bd3dd 100644 --- a/packages/-ember-data/tests/integration/application-test.js +++ b/packages/-ember-data/tests/integration/application-test.js @@ -147,6 +147,9 @@ module('integration/application - Attaching initializer', function(hooks) { this.owner = this.application.buildInstance(); let store = this.owner.lookup('service:store'); + assert.expectDeprecation({ + id: 'ember-data:-legacy-test-registrations', + }); assert.ok( store && store.get('isCustomStore'), 'ember-data initializer does not overwrite the previous registered service store' diff --git a/packages/-ember-data/tests/integration/lifecycle-hooks-test.js b/packages/-ember-data/tests/integration/lifecycle-hooks-test.js index 5015e47ca97..b1ff5c3f25c 100644 --- a/packages/-ember-data/tests/integration/lifecycle-hooks-test.js +++ b/packages/-ember-data/tests/integration/lifecycle-hooks-test.js @@ -57,6 +57,7 @@ module('integration/lifecycle_hooks - Lifecycle Hooks', function(hooks) { 'When the adapter acknowledges that a record has been created without a new data payload, a `didCreate` event is triggered.', { id: 'ember-data:evented-api-usage', + count: 1, until: '4.0', }, async function(assert) { diff --git a/packages/-ember-data/tests/integration/record-data/record-data-errors-test.ts b/packages/-ember-data/tests/integration/record-data/record-data-errors-test.ts index 05cab54dce1..7d30f3c0c82 100644 --- a/packages/-ember-data/tests/integration/record-data/record-data-errors-test.ts +++ b/packages/-ember-data/tests/integration/record-data/record-data-errors-test.ts @@ -9,6 +9,7 @@ import { JsonApiValidationError } from '@ember-data/store/-private/ts-interfaces import RecordData from '@ember-data/store/-private/ts-interfaces/record-data'; import { RecordIdentifier } from '@ember-data/store/-private/ts-interfaces/identifier'; import { RECORD_DATA_ERRORS } from '@ember-data/canary-features'; +import JSONAPISerializer from '@ember-data/serializer/json-api'; class Person extends Model { // TODO fix the typing for naked attrs @@ -109,6 +110,7 @@ module('integration/record-data - Custom RecordData Errors', function(hooks) { owner.register('model:person', Person); owner.unregister('service:store'); owner.register('service:store', CustomStore); + owner.register('serializer:application', JSONAPISerializer); }); test('Record Data invalid errors', async function(assert) { diff --git a/packages/-ember-data/tests/integration/record-data/record-data-state-test.ts b/packages/-ember-data/tests/integration/record-data/record-data-state-test.ts index 9ff39527c1d..1f20738b265 100644 --- a/packages/-ember-data/tests/integration/record-data/record-data-state-test.ts +++ b/packages/-ember-data/tests/integration/record-data/record-data-state-test.ts @@ -7,6 +7,7 @@ import { attr } from '@ember-data/model'; import Ember from 'ember'; import RecordData from '@ember-data/store/-private/ts-interfaces/record-data'; import { RECORD_DATA_STATE } from '@ember-data/canary-features'; +import JSONAPISerializer from '@ember-data/serializer/json-api'; class Person extends Model { // TODO fix the typing for naked attrs @@ -105,6 +106,7 @@ module('integration/record-data - Record Data State', function(hooks) { owner.register('model:person', Person); owner.unregister('service:store'); owner.register('service:store', CustomStore); + owner.register('serializer:application', JSONAPISerializer); }); test('Record Data state saving', async function(assert) { diff --git a/packages/-ember-data/tests/integration/relationships/has-many-test.js b/packages/-ember-data/tests/integration/relationships/has-many-test.js index 4ecef75b41a..1424c257ffd 100644 --- a/packages/-ember-data/tests/integration/relationships/has-many-test.js +++ b/packages/-ember-data/tests/integration/relationships/has-many-test.js @@ -1300,6 +1300,7 @@ module('integration/relationships/has_many - Has-Many Relationships', function(h 'PromiseArray proxies evented methods to its ManyArray', { id: 'ember-data:evented-api-usage', + count: 3, until: '4.0', }, function(assert) { diff --git a/packages/-ember-data/tests/integration/relationships/inverse-relationships-test.js b/packages/-ember-data/tests/integration/relationships/inverse-relationships-test.js index 7345501d002..6d639c4b57a 100644 --- a/packages/-ember-data/tests/integration/relationships/inverse-relationships-test.js +++ b/packages/-ember-data/tests/integration/relationships/inverse-relationships-test.js @@ -627,7 +627,7 @@ module('integration/relationships/inverse_relationships - Inverse Relationships' assert.expectAssertion(() => { store.createRecord('user', { post: null }); - }, /No model was found for/); + }, /No model was found for 'post' and no schema handles the type/); // but don't error if the relationship is not used store.createRecord('user', {}); diff --git a/packages/-ember-data/tests/integration/request-state-service-test.ts b/packages/-ember-data/tests/integration/request-state-service-test.ts index 2f3db0b98c0..de739341a88 100644 --- a/packages/-ember-data/tests/integration/request-state-service-test.ts +++ b/packages/-ember-data/tests/integration/request-state-service-test.ts @@ -7,6 +7,7 @@ import EmberObject from '@ember/object'; import { attr } from '@ember-data/model'; import { REQUEST_SERVICE } from '@ember-data/canary-features'; import { RequestStateEnum } from '@ember-data/store/-private/ts-interfaces/fetch-manager'; +import JSONSerializer from '@ember-data/serializer/json'; class Person extends Model { // TODO fix the typing for naked attrs @@ -26,6 +27,7 @@ if (REQUEST_SERVICE) { hooks.beforeEach(function() { let { owner } = this; owner.register('model:person', Person); + owner.register('serializer:application', JSONSerializer); store = owner.lookup('service:store'); }); diff --git a/packages/-ember-data/tests/integration/store/adapter-for-test.js b/packages/-ember-data/tests/integration/store/adapter-for-test.js index a4db1ccd00d..1160257932e 100644 --- a/packages/-ember-data/tests/integration/store/adapter-for-test.js +++ b/packages/-ember-data/tests/integration/store/adapter-for-test.js @@ -42,6 +42,9 @@ module('integration/store - adapterFor', function(hooks) { assert.expectAssertion(() => { store.adapterFor('person'); }, /Assertion Failed: No adapter was found for 'person' and no 'application' adapter was found as a fallback/); + assert.expectDeprecation({ + id: 'ember-data:-legacy-test-registrations', + }); }); test('we find and instantiate the application adapter', async function(assert) { diff --git a/packages/-ember-data/tests/integration/store/serializer-for-test.js b/packages/-ember-data/tests/integration/store/serializer-for-test.js index 1e50b793375..3aaf2c8bf7c 100644 --- a/packages/-ember-data/tests/integration/store/serializer-for-test.js +++ b/packages/-ember-data/tests/integration/store/serializer-for-test.js @@ -187,7 +187,8 @@ module('integration/store - serializerFor', function(hooks) { deprecatedTest( 'we can specify a fallback serializer on the adapter when there is no application serializer', { - id: 'ember-data:default-serializers', + id: 'ember-data:default-serializer', + count: 1, until: '4.0', }, async function(assert) { @@ -233,7 +234,7 @@ module('integration/store - serializerFor', function(hooks) { deprecatedTest( 'specifying defaultSerializer on the application adapter when there is a per-type serializer does not work', { - id: 'ember-data:default-serializers', + id: 'ember-data:default-serializer', until: '4.0', }, async function(assert) { @@ -309,7 +310,7 @@ module('integration/store - serializerFor', function(hooks) { deprecatedTest( 'specifying defaultSerializer on a fallback adapter when there is no per-type serializer does work', { - id: 'ember-data:default-serializers', + id: 'ember-data:default-serializer', until: '4.0', }, async function(assert) { @@ -374,7 +375,8 @@ module('integration/store - serializerFor', function(hooks) { deprecatedTest( 'When the per-type, application and adapter specified fallback serializer do not exist, we fallback to the -default serializer', { - id: 'ember-data:default-serializers', + id: 'ember-data:default-serializer', + count: 4, until: '4.0', }, async function(assert) { diff --git a/packages/-ember-data/tests/test-helper.js b/packages/-ember-data/tests/test-helper.js index baefbda919a..16cfbb482e9 100644 --- a/packages/-ember-data/tests/test-helper.js +++ b/packages/-ember-data/tests/test-helper.js @@ -8,7 +8,6 @@ import { DEBUG } from '@glimmer/env'; import QUnit from 'qunit'; import { wait, asyncEqual, invokeAsync } from 'dummy/tests/helpers/async'; import configureAsserts from 'dummy/tests/helpers/qunit-asserts'; -import { SHOULD_ASSERT_ALL } from './helpers/deprecated-test'; configureAsserts(); @@ -41,9 +40,7 @@ QUnit.begin(() => { const hooks = (mod.hooks.afterEach = mod.hooks.afterEach || []); if (mod.tests.length !== 0) { - if (SHOULD_ASSERT_ALL) { - hooks.unshift(assertAllDeprecations); - } + hooks.unshift(assertAllDeprecations); } }); } diff --git a/packages/-ember-data/tests/unit/adapters/rest-adapter/ajax-options-test.js b/packages/-ember-data/tests/unit/adapters/rest-adapter/ajax-options-test.js index 39ed014c433..44d7a7edab5 100644 --- a/packages/-ember-data/tests/unit/adapters/rest-adapter/ajax-options-test.js +++ b/packages/-ember-data/tests/unit/adapters/rest-adapter/ajax-options-test.js @@ -194,6 +194,33 @@ module('unit/adapters/rest-adapter/ajax-options - building requests', function(h }); }); + test('ajaxOptions() headers take precedence over adapter headers', function(assert) { + let store = this.owner.lookup('service:store'); + let adapter = store.adapterFor('application'); + + adapter.headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + + let url = 'example.com'; + let type = 'POST'; + let ajaxOptions = adapter.ajaxOptions(url, type, { + headers: { + 'Content-Type': 'application/json', + }, + }); + + assert.deepEqual(ajaxOptions, { + credentials: 'same-origin', + type: 'POST', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + url: 'example.com', + }); + }); + test('_fetchRequest() returns a promise', function(assert) { let store = this.owner.lookup('service:store'); let adapter = store.adapterFor('application'); diff --git a/packages/-ember-data/tests/unit/custom-class-support/custom-class-model-test.ts b/packages/-ember-data/tests/unit/custom-class-support/custom-class-model-test.ts index 493b2c738ed..c5deb30f95b 100644 --- a/packages/-ember-data/tests/unit/custom-class-support/custom-class-model-test.ts +++ b/packages/-ember-data/tests/unit/custom-class-support/custom-class-model-test.ts @@ -54,7 +54,6 @@ if (CUSTOM_MODEL_CLASS) { teardownRecord(record) {}, }); - owner.register('model:person', Person); owner.register( 'adapter:application', JSONAPIAdapter.extend({ @@ -62,7 +61,7 @@ if (CUSTOM_MODEL_CLASS) { createRecord: () => RSVP.reject(), }) ); - owner.register('serializer:-default', JSONAPISerializer); + owner.register('serializer:application', JSONAPISerializer); owner.unregister('service:store'); }); diff --git a/packages/-ember-data/tests/unit/model-test.js b/packages/-ember-data/tests/unit/model-test.js index 0aac3d349d6..f9b986175f1 100644 --- a/packages/-ember-data/tests/unit/model-test.js +++ b/packages/-ember-data/tests/unit/model-test.js @@ -1,4 +1,3 @@ -import { DEBUG } from '@glimmer/env'; import { guidFor } from '@ember/object/internals'; import { resolve, reject } from 'rsvp'; import { set, get, observer, computed } from '@ember/object'; @@ -597,6 +596,7 @@ module('unit/model - Model', function(hooks) { 'an event listener can be added to a record', { id: 'ember-data:evented-api-usage', + count: 1, until: '4.0', }, async function(assert) { @@ -609,10 +609,6 @@ module('unit/model - Model', function(hooks) { record.on('event!', F); - if (DEBUG) { - assert.expectDeprecation(/Called event! on person/); - } - record.trigger('event!'); await settled(); @@ -631,6 +627,7 @@ module('unit/model - Model', function(hooks) { 'when an event is triggered on a record the method with the same name is invoked with arguments', { id: 'ember-data:evented-api-usage', + count: 0, until: '4.0', }, async function(assert) { @@ -654,6 +651,7 @@ module('unit/model - Model', function(hooks) { 'when a method is invoked from an event with the same name the arguments are passed through', { id: 'ember-data:evented-api-usage', + count: 0, until: '4.0', }, async function(assert) { @@ -915,56 +913,70 @@ module('unit/model - Model', function(hooks) { }); module('toJSON()', function(hooks) { - test('A Model can be JSONified', async function(assert) { - let record = store.createRecord('person', { name: 'TomHuda' }); + deprecatedTest( + 'A Model can be JSONified', + { + id: 'ember-data:model.toJSON', + until: '4.0', + }, + async function(assert) { + let record = store.createRecord('person', { name: 'TomHuda' }); - assert.deepEqual(record.toJSON(), { - data: { - type: 'people', - attributes: { - name: 'TomHuda', - 'is-archived': undefined, - 'is-drug-addict': false, + assert.deepEqual(record.toJSON(), { + data: { + type: 'people', + attributes: { + name: 'TomHuda', + 'is-archived': undefined, + 'is-drug-addict': false, + }, }, - }, - }); - }); - - test('toJSON looks up the JSONSerializer using the store instead of using JSONSerializer.create', async function(assert) { - class Author extends Model { - @hasMany('post', { async: false, inverse: 'author' }) - posts; - } - class Post extends Model { - @belongsTo('author', { async: false, inverse: 'posts' }) - author; + }); } - this.owner.register('model:author', Author); - this.owner.register('model:post', Post); + ); - // Loading the person without explicitly - // loading its relationships seems to trigger the - // original bug where `this.store` was not - // present on the serializer due to using .create - // instead of `store.serializerFor`. - let person = store.push({ - data: { - type: 'author', - id: '1', - }, - }); + deprecatedTest( + 'toJSON looks up the JSONSerializer using the store instead of using JSONSerializer.create', + { + id: 'ember-data:model.toJSON', + until: '4.0', + }, + async function(assert) { + class Author extends Model { + @hasMany('post', { async: false, inverse: 'author' }) + posts; + } + class Post extends Model { + @belongsTo('author', { async: false, inverse: 'posts' }) + author; + } + this.owner.register('model:author', Author); + this.owner.register('model:post', Post); + + // Loading the person without explicitly + // loading its relationships seems to trigger the + // original bug where `this.store` was not + // present on the serializer due to using .create + // instead of `store.serializerFor`. + let person = store.push({ + data: { + type: 'author', + id: '1', + }, + }); - let errorThrown = false; - let json; - try { - json = person.toJSON(); - } catch (e) { - errorThrown = true; - } + let errorThrown = false; + let json; + try { + json = person.toJSON(); + } catch (e) { + errorThrown = true; + } - assert.ok(!errorThrown, 'error not thrown due to missing store'); - assert.deepEqual(json, { data: { type: 'authors' } }); - }); + assert.ok(!errorThrown, 'error not thrown due to missing store'); + assert.deepEqual(json, { data: { type: 'authors' } }); + } + ); }); module('Updating', function() { diff --git a/packages/-ember-data/tests/unit/model/lifecycle-callbacks-test.js b/packages/-ember-data/tests/unit/model/lifecycle-callbacks-test.js index 3b2c982636a..ae73779a894 100644 --- a/packages/-ember-data/tests/unit/model/lifecycle-callbacks-test.js +++ b/packages/-ember-data/tests/unit/model/lifecycle-callbacks-test.js @@ -3,299 +3,349 @@ import { get } from '@ember/object'; import { run } from '@ember/runloop'; import { setupTest } from 'ember-qunit'; -import { module, test } from 'qunit'; +import { module } from 'qunit'; import Adapter from '@ember-data/adapter'; import JSONAPISerializer from '@ember-data/serializer/json-api'; import Model, { attr } from '@ember-data/model'; import { InvalidError } from '@ember-data/adapter/error'; +import { deprecatedTest } from '../../helpers/deprecated-test'; module('unit/model/lifecycle_callbacks - Lifecycle Callbacks', function(hooks) { setupTest(hooks); - test('a record receives a didLoad callback when it has finished loading', function(assert) { - assert.expect(3); - - const Person = Model.extend({ - name: attr(), - didLoad() { - assert.ok('The didLoad callback was called'); - }, - }); + deprecatedTest( + 'a record receives a didLoad callback when it has finished loading', + { + id: 'ember-data:record-lifecycle-event-methods', + until: '4.0', + }, + function(assert) { + assert.expect(3); + + const Person = Model.extend({ + name: attr(), + didLoad() { + assert.ok('The didLoad callback was called'); + }, + }); - const ApplicationAdapter = Adapter.extend({ - findRecord(store, type, id, snapshot) { - return { data: { id: 1, type: 'person', attributes: { name: 'Foo' } } }; - }, - }); + const ApplicationAdapter = Adapter.extend({ + findRecord(store, type, id, snapshot) { + return { data: { id: 1, type: 'person', attributes: { name: 'Foo' } } }; + }, + }); - this.owner.register('model:person', Person); - this.owner.register('adapter:application', ApplicationAdapter); - this.owner.register('serializer:application', JSONAPISerializer.extend()); + this.owner.register('model:person', Person); + this.owner.register('adapter:application', ApplicationAdapter); + this.owner.register('serializer:application', JSONAPISerializer.extend()); - let store = this.owner.lookup('service:store'); + let store = this.owner.lookup('service:store'); - return run(() => { - return store.findRecord('person', 1).then(person => { - assert.equal(person.get('id'), '1', `The person's ID is available`); - assert.equal(person.get('name'), 'Foo', `The person's properties are availablez`); + return run(() => { + return store.findRecord('person', 1).then(person => { + assert.equal(person.get('id'), '1', `The person's ID is available`); + assert.equal(person.get('name'), 'Foo', `The person's properties are availablez`); + }); + }); + } + ); + + deprecatedTest( + `a record receives a didLoad callback once it materializes if it wasn't materialized when loaded`, + { + id: 'ember-data:record-lifecycle-event-methods', + until: '4.0', + }, + function(assert) { + assert.expect(2); + + let didLoadCalled = 0; + const Person = Model.extend({ + name: attr(), + didLoad() { + didLoadCalled++; + }, }); - }); - }); - - test(`TEMPORARY: a record receives a didLoad callback once it materializes if it wasn't materialized when loaded`, function(assert) { - assert.expect(2); - - let didLoadCalled = 0; - const Person = Model.extend({ - name: attr(), - didLoad() { - didLoadCalled++; - }, - }); - this.owner.register('model:person', Person); + this.owner.register('model:person', Person); - let store = this.owner.lookup('service:store'); + let store = this.owner.lookup('service:store'); - run(() => { - store._pushInternalModel({ id: 1, type: 'person' }); - assert.equal(didLoadCalled, 0, 'didLoad was not called'); - }); + run(() => { + store._pushInternalModel({ id: 1, type: 'person' }); + assert.equal(didLoadCalled, 0, 'didLoad was not called'); + }); - run(() => store.peekRecord('person', 1)); + run(() => store.peekRecord('person', 1)); - assert.equal(didLoadCalled, 1, 'didLoad was called'); - }); + assert.equal(didLoadCalled, 1, 'didLoad was called'); + } + ); - test('a record receives a didUpdate callback when it has finished updating', function(assert) { - assert.expect(5); + deprecatedTest( + 'a record receives a didUpdate callback when it has finished updating', + { + id: 'ember-data:record-lifecycle-event-methods', + until: '4.0', + }, + function(assert) { + assert.expect(5); - let callCount = 0; + let callCount = 0; - const Person = Model.extend({ - bar: attr('string'), - name: attr('string'), + const Person = Model.extend({ + bar: attr('string'), + name: attr('string'), - didUpdate() { - callCount++; - assert.equal(get(this, 'isSaving'), false, 'record should be saving'); - assert.equal(get(this, 'hasDirtyAttributes'), false, 'record should not be dirty'); - }, - }); + didUpdate() { + callCount++; + assert.equal(get(this, 'isSaving'), false, 'record should be saving'); + assert.equal(get(this, 'hasDirtyAttributes'), false, 'record should not be dirty'); + }, + }); - const ApplicationAdapter = Adapter.extend({ - findRecord(store, type, id, snapshot) { - return { data: { id: 1, type: 'person', attributes: { name: 'Foo' } } }; - }, + const ApplicationAdapter = Adapter.extend({ + findRecord(store, type, id, snapshot) { + return { data: { id: 1, type: 'person', attributes: { name: 'Foo' } } }; + }, - updateRecord(store, type, snapshot) { - assert.equal(callCount, 0, 'didUpdate callback was not called until didSaveRecord is called'); - return resolve(); - }, - }); + updateRecord(store, type, snapshot) { + assert.equal(callCount, 0, 'didUpdate callback was not called until didSaveRecord is called'); + return resolve(); + }, + }); - this.owner.register('model:person', Person); - this.owner.register('adapter:application', ApplicationAdapter); - this.owner.register('serializer:application', JSONAPISerializer.extend()); + this.owner.register('model:person', Person); + this.owner.register('adapter:application', ApplicationAdapter); + this.owner.register('serializer:application', JSONAPISerializer.extend()); - let asyncPerson = run(() => this.owner.lookup('service:store').findRecord('person', 1)); + let asyncPerson = run(() => this.owner.lookup('service:store').findRecord('person', 1)); - assert.equal(callCount, 0, 'precond - didUpdate callback was not called yet'); + assert.equal(callCount, 0, 'precond - didUpdate callback was not called yet'); - return run(() => { - return asyncPerson - .then(person => { - return run(() => { - person.set('bar', 'Bar'); - return person.save(); + return run(() => { + return asyncPerson + .then(person => { + return run(() => { + person.set('bar', 'Bar'); + return person.save(); + }); + }) + .then(() => { + assert.equal(callCount, 1, 'didUpdate called after update'); }); - }) - .then(() => { - assert.equal(callCount, 1, 'didUpdate called after update'); - }); - }); - }); - - test('a record receives a didCreate callback when it has finished updating', function(assert) { - assert.expect(5); - - let callCount = 0; - - const Person = Model.extend({ - didCreate() { - callCount++; - assert.equal(get(this, 'isSaving'), false, 'record should not be saving'); - assert.equal(get(this, 'hasDirtyAttributes'), false, 'record should not be dirty'); - }, - }); - - const ApplicationAdapter = Adapter.extend({ - createRecord(store, type, snapshot) { - assert.equal(callCount, 0, 'didCreate callback was not called until didSaveRecord is called'); - return resolve(); - }, - }); - - this.owner.register('model:person', Person); - this.owner.register('adapter:application', ApplicationAdapter); - this.owner.register('serializer:application', JSONAPISerializer.extend()); - - assert.equal(callCount, 0, 'precond - didCreate callback was not called yet'); - - let person = this.owner.lookup('service:store').createRecord('person', { - id: 69, - name: 'Newt Gingrich', - }); - - return run(() => { - return person.save().then(() => { - assert.equal(callCount, 1, 'didCreate called after commit'); }); - }); - }); + } + ); + + deprecatedTest( + 'a record receives a didCreate callback when it has finished updating', + { + id: 'ember-data:record-lifecycle-event-methods', + until: '4.0', + }, + function(assert) { + assert.expect(5); + + let callCount = 0; + + const Person = Model.extend({ + didCreate() { + callCount++; + assert.equal(get(this, 'isSaving'), false, 'record should not be saving'); + assert.equal(get(this, 'hasDirtyAttributes'), false, 'record should not be dirty'); + }, + }); - test('a record receives a didDelete callback when it has finished deleting', function(assert) { - assert.expect(5); + const ApplicationAdapter = Adapter.extend({ + createRecord(store, type, snapshot) { + assert.equal(callCount, 0, 'didCreate callback was not called until didSaveRecord is called'); + return resolve(); + }, + }); - let callCount = 0; + this.owner.register('model:person', Person); + this.owner.register('adapter:application', ApplicationAdapter); + this.owner.register('serializer:application', JSONAPISerializer.extend()); - const Person = Model.extend({ - bar: attr('string'), - name: attr('string'), + assert.equal(callCount, 0, 'precond - didCreate callback was not called yet'); - didDelete() { - callCount++; + let person = this.owner.lookup('service:store').createRecord('person', { + id: 69, + name: 'Newt Gingrich', + }); - assert.equal(get(this, 'isSaving'), false, 'record should not be saving'); - assert.equal(get(this, 'hasDirtyAttributes'), false, 'record should not be dirty'); - }, - }); + return run(() => { + return person.save().then(() => { + assert.equal(callCount, 1, 'didCreate called after commit'); + }); + }); + } + ); + + deprecatedTest( + 'a record receives a didDelete callback when it has finished deleting', + { + id: 'ember-data:record-lifecycle-event-methods', + until: '4.0', + }, + function(assert) { + assert.expect(5); + + let callCount = 0; + + const Person = Model.extend({ + bar: attr('string'), + name: attr('string'), + + didDelete() { + callCount++; + + assert.equal(get(this, 'isSaving'), false, 'record should not be saving'); + assert.equal(get(this, 'hasDirtyAttributes'), false, 'record should not be dirty'); + }, + }); - const ApplicationAdapter = Adapter.extend({ - findRecord(store, type, id, snapshot) { - return { data: { id: 1, type: 'person', attributes: { name: 'Foo' } } }; - }, + const ApplicationAdapter = Adapter.extend({ + findRecord(store, type, id, snapshot) { + return { data: { id: 1, type: 'person', attributes: { name: 'Foo' } } }; + }, - deleteRecord(store, type, snapshot) { - assert.equal(callCount, 0, 'didDelete callback was not called until didSaveRecord is called'); + deleteRecord(store, type, snapshot) { + assert.equal(callCount, 0, 'didDelete callback was not called until didSaveRecord is called'); - return resolve(); - }, - }); + return resolve(); + }, + }); - this.owner.register('model:person', Person); - this.owner.register('adapter:application', ApplicationAdapter); - this.owner.register('serializer:application', JSONAPISerializer.extend()); + this.owner.register('model:person', Person); + this.owner.register('adapter:application', ApplicationAdapter); + this.owner.register('serializer:application', JSONAPISerializer.extend()); - let asyncPerson = run(() => this.owner.lookup('service:store').findRecord('person', 1)); + let asyncPerson = run(() => this.owner.lookup('service:store').findRecord('person', 1)); - assert.equal(callCount, 0, 'precond - didDelete callback was not called yet'); + assert.equal(callCount, 0, 'precond - didDelete callback was not called yet'); - return run(() => { - return asyncPerson - .then(person => { - return run(() => { - person.deleteRecord(); - return person.save(); + return run(() => { + return asyncPerson + .then(person => { + return run(() => { + person.deleteRecord(); + return person.save(); + }); + }) + .then(() => { + assert.equal(callCount, 1, 'didDelete called after delete'); }); - }) - .then(() => { - assert.equal(callCount, 1, 'didDelete called after delete'); - }); - }); - }); - - test('an uncommited record also receives a didDelete callback when it is deleted', function(assert) { - assert.expect(4); - - let callCount = 0; + }); + } + ); + + deprecatedTest( + 'an uncommited record also receives a didDelete callback when it is deleted', + { + id: 'ember-data:record-lifecycle-event-methods', + until: '4.0', + }, + function(assert) { + assert.expect(4); + + let callCount = 0; + + const Person = Model.extend({ + bar: attr('string'), + name: attr('string'), + + didDelete() { + callCount++; + assert.equal(get(this, 'isSaving'), false, 'record should not be saving'); + assert.equal(get(this, 'hasDirtyAttributes'), false, 'record should not be dirty'); + }, + }); - const Person = Model.extend({ - bar: attr('string'), - name: attr('string'), + this.owner.register('model:person', Person); - didDelete() { - callCount++; - assert.equal(get(this, 'isSaving'), false, 'record should not be saving'); - assert.equal(get(this, 'hasDirtyAttributes'), false, 'record should not be dirty'); - }, - }); + let person = this.owner.lookup('service:store').createRecord('person', { + name: 'Tomster', + }); - this.owner.register('model:person', Person); + assert.equal(callCount, 0, 'precond - didDelete callback was not called yet'); - let person = this.owner.lookup('service:store').createRecord('person', { - name: 'Tomster', - }); + run(() => person.deleteRecord()); - assert.equal(callCount, 0, 'precond - didDelete callback was not called yet'); + assert.equal(callCount, 1, 'didDelete called after delete'); + } + ); - run(() => person.deleteRecord()); + deprecatedTest( + 'a record receives a becameInvalid callback when it became invalid', + { + id: 'ember-data:record-lifecycle-event-methods', + until: '4.0', + }, + function(assert) { + assert.expect(8); - assert.equal(callCount, 1, 'didDelete called after delete'); - }); + let callCount = 0; - test('a record receives a becameInvalid callback when it became invalid', function(assert) { - assert.expect(8); + const Person = Model.extend({ + bar: attr('string'), + name: attr('string'), - let callCount = 0; + becameInvalid() { + callCount++; - const Person = Model.extend({ - bar: attr('string'), - name: attr('string'), + assert.equal(get(this, 'isSaving'), false, 'record should not be saving'); + assert.equal(get(this, 'hasDirtyAttributes'), true, 'record should be dirty'); + }, + }); - becameInvalid() { - callCount++; + const ApplicationAdapter = Adapter.extend({ + findRecord(store, type, id, snapshot) { + return { data: { id: 1, type: 'person', attributes: { name: 'Foo' } } }; + }, + + updateRecord(store, type, snapshot) { + assert.equal(callCount, 0, 'becameInvalid callback was not called until recordWasInvalid is called'); + + return reject( + new InvalidError([ + { + title: 'Invalid Attribute', + detail: 'error', + source: { + pointer: '/data/attributes/bar', + }, + }, + ]) + ); + }, + }); - assert.equal(get(this, 'isSaving'), false, 'record should not be saving'); - assert.equal(get(this, 'hasDirtyAttributes'), true, 'record should be dirty'); - }, - }); + this.owner.register('model:person', Person); + this.owner.register('adapter:application', ApplicationAdapter); + this.owner.register('serializer:application', JSONAPISerializer.extend()); - const ApplicationAdapter = Adapter.extend({ - findRecord(store, type, id, snapshot) { - return { data: { id: 1, type: 'person', attributes: { name: 'Foo' } } }; - }, + let asyncPerson = run(() => this.owner.lookup('service:store').findRecord('person', 1)); + assert.equal(callCount, 0, 'precond - becameInvalid callback was not called yet'); - updateRecord(store, type, snapshot) { - assert.equal(callCount, 0, 'becameInvalid callback was not called until recordWasInvalid is called'); + // Make sure that the error handler has a chance to attach before + // save fails. + return run(() => { + return asyncPerson.then(person => { + return run(() => { + person.set('bar', 'Bar'); + return person.save().catch(reason => { + assert.ok(reason.isAdapterError, 'reason should have been an adapter error'); - return reject( - new InvalidError([ - { - title: 'Invalid Attribute', - detail: 'error', - source: { - pointer: '/data/attributes/bar', - }, - }, - ]) - ); - }, - }); - - this.owner.register('model:person', Person); - this.owner.register('adapter:application', ApplicationAdapter); - this.owner.register('serializer:application', JSONAPISerializer.extend()); - - let asyncPerson = run(() => this.owner.lookup('service:store').findRecord('person', 1)); - assert.equal(callCount, 0, 'precond - becameInvalid callback was not called yet'); - - // Make sure that the error handler has a chance to attach before - // save fails. - return run(() => { - return asyncPerson.then(person => { - return run(() => { - person.set('bar', 'Bar'); - return person.save().catch(reason => { - assert.ok(reason.isAdapterError, 'reason should have been an adapter error'); - - assert.equal(reason.errors.length, 1, 'reason should have one error'); - assert.equal(reason.errors[0].title, 'Invalid Attribute'); - assert.equal(callCount, 1, 'becameInvalid called after invalidating'); + assert.equal(reason.errors.length, 1, 'reason should have one error'); + assert.equal(reason.errors[0].title, 'Invalid Attribute'); + assert.equal(callCount, 1, 'becameInvalid called after invalidating'); + }); }); }); }); - }); - }); + } + ); }); diff --git a/packages/-ember-data/tests/unit/model/rollback-attributes-test.js b/packages/-ember-data/tests/unit/model/rollback-attributes-test.js index deaefb6008e..c670fac01d7 100644 --- a/packages/-ember-data/tests/unit/model/rollback-attributes-test.js +++ b/packages/-ember-data/tests/unit/model/rollback-attributes-test.js @@ -18,450 +18,451 @@ import { settled } from '@ember/test-helpers'; module('unit/model/rollbackAttributes - model.rollbackAttributes()', function(hooks) { setupTest(hooks); - hooks.beforeEach(function() { - const Person = DS.Model.extend({ - firstName: DS.attr(), - lastName: DS.attr(), - rolledBackCount: 0, - rolledBack() { - this.incrementProperty('rolledBackCount'); - }, - }); - Person.reopenClass({ - toString() { - return 'Person'; - }, - }); - - this.owner.register('model:person', Person); - this.owner.register('adapter:application', Adapter.extend()); - this.owner.register('serializer:application', JSONAPISerializer.extend()); - }); - - test('changes to attributes can be rolled back', function(assert) { - let store = this.owner.lookup('service:store'); - let person; - - run(() => { - store.push({ - data: { - type: 'person', - id: '1', - attributes: { - firstName: 'Tom', - lastName: 'Dale', - }, + module('rolledBack hook', function(hooks) { + hooks.beforeEach(function() { + const Person = DS.Model.extend({ + firstName: DS.attr(), + lastName: DS.attr(), + rolledBackCount: 0, + rolledBack() { + this.incrementProperty('rolledBackCount'); }, }); - person = store.peekRecord('person', 1); - person.set('firstName', 'Thomas'); - return person; - }); - - assert.equal(person.get('firstName'), 'Thomas'); - assert.equal(person.get('rolledBackCount'), 0); - - run(() => person.rollbackAttributes()); - - assert.equal(person.get('firstName'), 'Tom'); - assert.equal(person.get('hasDirtyAttributes'), false); - assert.equal(person.get('rolledBackCount'), 1); - }); - - test('changes to unassigned attributes can be rolled back', function(assert) { - let store = this.owner.lookup('service:store'); - let person; - - run(() => { - store.push({ - data: { - type: 'person', - id: '1', - attributes: { - lastName: 'Dale', - }, + Person.reopenClass({ + toString() { + return 'Person'; }, }); - person = store.peekRecord('person', 1); - person.set('firstName', 'Thomas'); - return person; + this.owner.register('model:person', Person); + this.owner.register('adapter:application', Adapter.extend()); + this.owner.register('serializer:application', JSONAPISerializer.extend()); + }); + hooks.afterEach(function(assert) { + assert.expectDeprecation({ + id: 'ember-data:record-lifecycle-event-methods', + }); }); - assert.equal(person.get('firstName'), 'Thomas'); - assert.equal(person.get('rolledBackCount'), 0); - - run(() => person.rollbackAttributes()); - - assert.strictEqual(person.get('firstName'), undefined); - assert.equal(person.get('hasDirtyAttributes'), false); - assert.equal(person.get('rolledBackCount'), 1); - }); - - test('changes to attributes made after a record is in-flight only rolls back the local changes', function(assert) { - let store = this.owner.lookup('service:store'); - let adapter = store.adapterFor('application'); - - adapter.updateRecord = function(store, type, snapshot) { - // Make sure the save is async - return new EmberPromise(resolve => later(null, resolve, 15)); - }; - - let person = run(() => { - store.push({ - data: { - type: 'person', - id: '1', - attributes: { - firstName: 'Tom', - lastName: 'Dale', + test('changes to attributes can be rolled back', function(assert) { + let store = this.owner.lookup('service:store'); + let person; + + run(() => { + store.push({ + data: { + type: 'person', + id: '1', + attributes: { + firstName: 'Tom', + lastName: 'Dale', + }, }, - }, + }); + person = store.peekRecord('person', 1); + person.set('firstName', 'Thomas'); + return person; }); - let person = store.peekRecord('person', 1); - person.set('firstName', 'Thomas'); + assert.equal(person.get('firstName'), 'Thomas'); + assert.equal(person.get('rolledBackCount'), 0); - return person; - }); + run(() => person.rollbackAttributes()); - return run(() => { - let saving = person.save(); + assert.equal(person.get('firstName'), 'Tom'); + assert.equal(person.get('hasDirtyAttributes'), false); + assert.equal(person.get('rolledBackCount'), 1); + }); - assert.equal(person.get('firstName'), 'Thomas'); + test('changes to unassigned attributes can be rolled back', function(assert) { + let store = this.owner.lookup('service:store'); + let person; + + run(() => { + store.push({ + data: { + type: 'person', + id: '1', + attributes: { + lastName: 'Dale', + }, + }, + }); + person = store.peekRecord('person', 1); + person.set('firstName', 'Thomas'); - person.set('lastName', 'Dolly'); + return person; + }); - assert.equal(person.get('lastName'), 'Dolly'); + assert.equal(person.get('firstName'), 'Thomas'); assert.equal(person.get('rolledBackCount'), 0); - person.rollbackAttributes(); + run(() => person.rollbackAttributes()); - assert.equal(person.get('firstName'), 'Thomas'); - assert.equal(person.get('lastName'), 'Dale'); - assert.equal(person.get('isSaving'), true); - - return saving.then(() => { - assert.equal(person.get('rolledBackCount'), 1); - assert.equal(person.get('hasDirtyAttributes'), false, 'The person is now clean'); - }); + assert.strictEqual(person.get('firstName'), undefined); + assert.equal(person.get('hasDirtyAttributes'), false); + assert.equal(person.get('rolledBackCount'), 1); }); - }); - test("a record's changes can be made if it fails to save", function(assert) { - let store = this.owner.lookup('service:store'); - let adapter = store.adapterFor('application'); + test('changes to attributes made after a record is in-flight only rolls back the local changes', function(assert) { + let store = this.owner.lookup('service:store'); + let adapter = store.adapterFor('application'); + + adapter.updateRecord = function(store, type, snapshot) { + // Make sure the save is async + return new EmberPromise(resolve => later(null, resolve, 15)); + }; + + let person = run(() => { + store.push({ + data: { + type: 'person', + id: '1', + attributes: { + firstName: 'Tom', + lastName: 'Dale', + }, + }, + }); - adapter.updateRecord = function(store, type, snapshot) { - return reject(); - }; + let person = store.peekRecord('person', 1); + person.set('firstName', 'Thomas'); - let person = run(() => { - store.push({ - data: { - type: 'person', - id: '1', - attributes: { - firstName: 'Tom', - lastName: 'Dale', - }, - }, + return person; }); - let person = store.peekRecord('person', 1); - person.set('firstName', 'Thomas'); + return run(() => { + let saving = person.save(); - return person; - }); + assert.equal(person.get('firstName'), 'Thomas'); - assert.deepEqual(person.changedAttributes().firstName, ['Tom', 'Thomas']); + person.set('lastName', 'Dolly'); - run(function() { - person.save().then(null, function() { - assert.equal(person.get('isError'), true); - assert.deepEqual(person.changedAttributes().firstName, ['Tom', 'Thomas']); + assert.equal(person.get('lastName'), 'Dolly'); assert.equal(person.get('rolledBackCount'), 0); - run(function() { - person.rollbackAttributes(); - }); + person.rollbackAttributes(); + + assert.equal(person.get('firstName'), 'Thomas'); + assert.equal(person.get('lastName'), 'Dale'); + assert.equal(person.get('isSaving'), true); - assert.equal(person.get('firstName'), 'Tom'); - assert.equal(person.get('isError'), false); - assert.equal(Object.keys(person.changedAttributes()).length, 0); - assert.equal(person.get('rolledBackCount'), 1); + return saving.then(() => { + assert.equal(person.get('rolledBackCount'), 1); + assert.equal(person.get('hasDirtyAttributes'), false, 'The person is now clean'); + }); }); }); - }); - - test(`a deleted record's attributes can be rollbacked if it fails to save, record arrays are updated accordingly`, function(assert) { - assert.expect(10); - let store = this.owner.lookup('service:store'); - let adapter = store.adapterFor('application'); - - adapter.deleteRecord = function(store, type, snapshot) { - return reject(); - }; + test("a record's changes can be made if it fails to save", function(assert) { + let store = this.owner.lookup('service:store'); + let adapter = store.adapterFor('application'); + + adapter.updateRecord = function(store, type, snapshot) { + return reject(); + }; + + let person = run(() => { + store.push({ + data: { + type: 'person', + id: '1', + attributes: { + firstName: 'Tom', + lastName: 'Dale', + }, + }, + }); - let person, people; + let person = store.peekRecord('person', 1); + person.set('firstName', 'Thomas'); - run(() => { - store.push({ - data: { - type: 'person', - id: '1', - attributes: { - firstName: 'Tom', - lastName: 'Dale', - }, - }, + return person; }); - person = store.peekRecord('person', 1); - people = store.peekAll('person'); - }); - run(() => person.deleteRecord()); + assert.deepEqual(person.changedAttributes().firstName, ['Tom', 'Thomas']); - assert.equal(people.get('length'), 1, 'a deleted record appears in record array until it is saved'); - assert.equal(people.objectAt(0), person, 'a deleted record appears in record array until it is saved'); - - return run(() => { - return person - .save() - .catch(() => { + run(function() { + person.save().then(null, function() { assert.equal(person.get('isError'), true); - assert.equal(person.get('isDeleted'), true); + assert.deepEqual(person.changedAttributes().firstName, ['Tom', 'Thomas']); assert.equal(person.get('rolledBackCount'), 0); - run(() => person.rollbackAttributes()); + run(function() { + person.rollbackAttributes(); + }); - assert.equal(person.get('isDeleted'), false); + assert.equal(person.get('firstName'), 'Tom'); assert.equal(person.get('isError'), false); - assert.equal(person.get('hasDirtyAttributes'), false, 'must be not dirty'); + assert.equal(Object.keys(person.changedAttributes()).length, 0); assert.equal(person.get('rolledBackCount'), 1); - }) - .then(() => { - assert.equal( - people.get('length'), - 1, - 'the underlying record array is updated accordingly in an asynchronous way' - ); }); + }); }); - }); - test(`new record's attributes can be rollbacked`, function(assert) { - let store = this.owner.lookup('service:store'); - let person = store.createRecord('person', { id: 1 }); + test(`a deleted record's attributes can be rollbacked if it fails to save, record arrays are updated accordingly`, function(assert) { + let store = this.owner.lookup('service:store'); + let adapter = store.adapterFor('application'); + + adapter.deleteRecord = function(store, type, snapshot) { + return reject(); + }; + + let person, people; + + run(() => { + store.push({ + data: { + type: 'person', + id: '1', + attributes: { + firstName: 'Tom', + lastName: 'Dale', + }, + }, + }); + person = store.peekRecord('person', 1); + people = store.peekAll('person'); + }); - assert.equal(person.get('isNew'), true, 'must be new'); - assert.equal(person.get('hasDirtyAttributes'), true, 'must be dirty'); - assert.equal(person.get('rolledBackCount'), 0); + run(() => person.deleteRecord()); + + assert.equal(people.get('length'), 1, 'a deleted record appears in record array until it is saved'); + assert.equal(people.objectAt(0), person, 'a deleted record appears in record array until it is saved'); + + return run(() => { + return person + .save() + .catch(() => { + assert.equal(person.get('isError'), true); + assert.equal(person.get('isDeleted'), true); + assert.equal(person.get('rolledBackCount'), 0); + + run(() => person.rollbackAttributes()); + + assert.equal(person.get('isDeleted'), false); + assert.equal(person.get('isError'), false); + assert.equal(person.get('hasDirtyAttributes'), false, 'must be not dirty'); + assert.equal(person.get('rolledBackCount'), 1); + }) + .then(() => { + assert.equal( + people.get('length'), + 1, + 'the underlying record array is updated accordingly in an asynchronous way' + ); + }); + }); + }); - run(person, 'rollbackAttributes'); + test(`new record's attributes can be rollbacked`, function(assert) { + let store = this.owner.lookup('service:store'); + let person = store.createRecord('person', { id: 1 }); - assert.equal(person.get('isNew'), false, 'must not be new'); - assert.equal(person.get('hasDirtyAttributes'), false, 'must not be dirty'); - assert.equal(person.get('isDeleted'), true, 'must be deleted'); - assert.equal(person.get('rolledBackCount'), 1); - }); + assert.equal(person.get('isNew'), true, 'must be new'); + assert.equal(person.get('hasDirtyAttributes'), true, 'must be dirty'); + assert.equal(person.get('rolledBackCount'), 0); - test(`invalid new record's attributes can be rollbacked`, function(assert) { - let error = new DS.InvalidError([ - { - detail: 'is invalid', - source: { pointer: 'data/attributes/name' }, - }, - ]); + run(person, 'rollbackAttributes'); - let adapter = DS.RESTAdapter.extend({ - ajax(url, type, hash) { - return reject(error); - }, + assert.equal(person.get('isNew'), false, 'must not be new'); + assert.equal(person.get('hasDirtyAttributes'), false, 'must not be dirty'); + assert.equal(person.get('isDeleted'), true, 'must be deleted'); + assert.equal(person.get('rolledBackCount'), 1); }); - this.owner.register('adapter:application', adapter); - this.owner.register('serializer:application', RESTSerializer.extend()); + test(`invalid new record's attributes can be rollbacked`, function(assert) { + let error = new DS.InvalidError([ + { + detail: 'is invalid', + source: { pointer: 'data/attributes/name' }, + }, + ]); - let store = this.owner.lookup('service:store'); - let person = store.createRecord('person', { id: 1 }); + let adapter = DS.RESTAdapter.extend({ + ajax(url, type, hash) { + return reject(error); + }, + }); - assert.equal(person.get('isNew'), true, 'must be new'); - assert.equal(person.get('hasDirtyAttributes'), true, 'must be dirty'); + this.owner.register('adapter:application', adapter); + this.owner.register('serializer:application', RESTSerializer.extend()); - return run(() => { - return person.save().catch(reason => { - assert.equal(error, reason); - assert.equal(person.get('isValid'), false); + let store = this.owner.lookup('service:store'); + let person = store.createRecord('person', { id: 1 }); - run(() => person.rollbackAttributes()); + assert.equal(person.get('isNew'), true, 'must be new'); + assert.equal(person.get('hasDirtyAttributes'), true, 'must be dirty'); - assert.equal(person.get('isNew'), false, 'must not be new'); - assert.equal(person.get('hasDirtyAttributes'), false, 'must not be dirty'); - assert.equal(person.get('isDeleted'), true, 'must be deleted'); - assert.equal(person.get('rolledBackCount'), 1); - }); - }); - }); + return run(() => { + return person.save().catch(reason => { + assert.equal(error, reason); + assert.equal(person.get('isValid'), false); - test(`invalid record's attributes can be rollbacked after multiple failed calls - #3677`, function(assert) { - let adapter = DS.RESTAdapter.extend({ - ajax(url, type, hash) { - let error = new DS.InvalidError(); - return reject(error); - }, - }); - - this.owner.register('adapter:application', adapter); - this.owner.register('serializer:application', RESTSerializer.extend()); + run(() => person.rollbackAttributes()); - let store = this.owner.lookup('service:store'); + assert.equal(person.get('isNew'), false, 'must not be new'); + assert.equal(person.get('hasDirtyAttributes'), false, 'must not be dirty'); + assert.equal(person.get('isDeleted'), true, 'must be deleted'); + assert.equal(person.get('rolledBackCount'), 1); + }); + }); + }); - let person; - run(() => { - person = store.push({ - data: { - type: 'person', - id: 1, - attributes: { - firstName: 'original name', - }, + test(`invalid record's attributes can be rollbacked after multiple failed calls - #3677`, function(assert) { + let adapter = DS.RESTAdapter.extend({ + ajax(url, type, hash) { + let error = new DS.InvalidError(); + return reject(error); }, }); - person.set('firstName', 'updated name'); - }); + this.owner.register('adapter:application', adapter); + this.owner.register('serializer:application', RESTSerializer.extend()); - return run(() => { - assert.equal(person.get('firstName'), 'updated name', 'precondition: firstName is changed'); + let store = this.owner.lookup('service:store'); - return person - .save() - .catch(() => { - assert.equal(person.get('hasDirtyAttributes'), true, 'has dirty attributes'); - assert.equal(person.get('firstName'), 'updated name', 'firstName is still changed'); + let person; + run(() => { + person = store.push({ + data: { + type: 'person', + id: 1, + attributes: { + firstName: 'original name', + }, + }, + }); - return person.save(); - }) - .catch(() => { - run(() => person.rollbackAttributes()); + person.set('firstName', 'updated name'); + }); - assert.equal(person.get('hasDirtyAttributes'), false, 'has no dirty attributes'); - assert.equal( - person.get('firstName'), - 'original name', - 'after rollbackAttributes() firstName has the original value' - ); - assert.equal(person.get('rolledBackCount'), 1); - }); + return run(() => { + assert.equal(person.get('firstName'), 'updated name', 'precondition: firstName is changed'); + + return person + .save() + .catch(() => { + assert.equal(person.get('hasDirtyAttributes'), true, 'has dirty attributes'); + assert.equal(person.get('firstName'), 'updated name', 'firstName is still changed'); + + return person.save(); + }) + .catch(() => { + run(() => person.rollbackAttributes()); + + assert.equal(person.get('hasDirtyAttributes'), false, 'has no dirty attributes'); + assert.equal( + person.get('firstName'), + 'original name', + 'after rollbackAttributes() firstName has the original value' + ); + assert.equal(person.get('rolledBackCount'), 1); + }); + }); }); - }); - test(`deleted record's attributes can be rollbacked`, function(assert) { - let store = this.owner.lookup('service:store'); + test(`deleted record's attributes can be rollbacked`, function(assert) { + let store = this.owner.lookup('service:store'); - let person, people; + let person, people; - run(() => { - store.push({ - data: { - type: 'person', - id: '1', - }, + run(() => { + store.push({ + data: { + type: 'person', + id: '1', + }, + }); + person = store.peekRecord('person', 1); + people = store.peekAll('person'); + person.deleteRecord(); }); - person = store.peekRecord('person', 1); - people = store.peekAll('person'); - person.deleteRecord(); - }); - assert.equal(people.get('length'), 1, 'a deleted record appears in the record array until it is saved'); - assert.equal(people.objectAt(0), person, 'a deleted record appears in the record array until it is saved'); + assert.equal(people.get('length'), 1, 'a deleted record appears in the record array until it is saved'); + assert.equal(people.objectAt(0), person, 'a deleted record appears in the record array until it is saved'); - assert.equal(person.get('isDeleted'), true, 'must be deleted'); + assert.equal(person.get('isDeleted'), true, 'must be deleted'); - run(() => person.rollbackAttributes()); + run(() => person.rollbackAttributes()); - assert.equal(people.get('length'), 1, 'the rollbacked record should appear again in the record array'); - assert.equal(person.get('isDeleted'), false, 'must not be deleted'); - assert.equal(person.get('hasDirtyAttributes'), false, 'must not be dirty'); - }); - - test("invalid record's attributes can be rollbacked", async function(assert) { - assert.expect(13); + assert.equal(people.get('length'), 1, 'the rollbacked record should appear again in the record array'); + assert.equal(person.get('isDeleted'), false, 'must not be deleted'); + assert.equal(person.get('hasDirtyAttributes'), false, 'must not be dirty'); + }); - class Dog extends Model { - @attr() name; - rolledBackCount = 0; - rolledBack() { - this.incrementProperty('rolledBackCount'); + test("invalid record's attributes can be rollbacked", async function(assert) { + class Dog extends Model { + @attr() name; + rolledBackCount = 0; + rolledBack() { + this.incrementProperty('rolledBackCount'); + } } - } - const thrownAdapterError = new InvalidError([ - { - detail: 'is invalid', - source: { pointer: 'data/attributes/name' }, - }, - ]); - class TestAdapter extends RESTAdapter { - ajax() { - return reject(thrownAdapterError); + const thrownAdapterError = new InvalidError([ + { + detail: 'is invalid', + source: { pointer: 'data/attributes/name' }, + }, + ]); + class TestAdapter extends RESTAdapter { + ajax() { + return reject(thrownAdapterError); + } } - } - const { owner } = this; - owner.register(`model:dog`, Dog); - owner.register(`adapter:application`, TestAdapter); - owner.register(`serializer:application`, RESTSerializer.extend()); - const store = owner.lookup(`service:store`); + const { owner } = this; + owner.register(`model:dog`, Dog); + owner.register(`adapter:application`, TestAdapter); + owner.register(`serializer:application`, RESTSerializer.extend()); + const store = owner.lookup(`service:store`); - const dog = store.push({ - data: { - type: 'dog', - id: '1', - attributes: { - name: 'Pluto', + const dog = store.push({ + data: { + type: 'dog', + id: '1', + attributes: { + name: 'Pluto', + }, }, - }, - }); - dog.set('name', 'is a dwarf planet'); + }); + dog.set('name', 'is a dwarf planet'); - addObserver(dog, 'errors.name', function() { - assert.ok(true, 'errors.name did change'); - }); + addObserver(dog, 'errors.name', function() { + assert.ok(true, 'errors.name did change'); + }); - dog.get('errors').addArrayObserver( - {}, - { - willChange() { - assert.ok(true, 'errors will change'); - }, - didChange() { - assert.ok(true, 'errors did change'); - }, - } - ); + dog.get('errors').addArrayObserver( + {}, + { + willChange() { + assert.ok(true, 'errors will change'); + }, + didChange() { + assert.ok(true, 'errors did change'); + }, + } + ); - try { - assert.ok(true, 'saving'); - await dog.save(); - } catch (reason) { - assert.equal(reason, thrownAdapterError, 'We threw the expected error during save'); + try { + assert.ok(true, 'saving'); + await dog.save(); + } catch (reason) { + assert.equal(reason, thrownAdapterError, 'We threw the expected error during save'); - dog.rollbackAttributes(); - await settled(); + dog.rollbackAttributes(); + await settled(); - assert.equal(dog.get('hasDirtyAttributes'), false, 'must not be dirty'); - assert.equal(dog.get('name'), 'Pluto', 'Name is rolled back'); - assert.notOk(dog.get('errors.name'), 'We have no errors for name anymore'); - assert.ok(dog.get('isValid'), 'We are now in a valid state'); - assert.equal(dog.get('rolledBackCount'), 1, 'we only rolled back once'); - } + assert.equal(dog.get('hasDirtyAttributes'), false, 'must not be dirty'); + assert.equal(dog.get('name'), 'Pluto', 'Name is rolled back'); + assert.notOk(dog.get('errors.name'), 'We have no errors for name anymore'); + assert.ok(dog.get('isValid'), 'We are now in a valid state'); + assert.equal(dog.get('rolledBackCount'), 1, 'we only rolled back once'); + } + }); }); test(`invalid record's attributes rolled back to correct state after set`, async function(assert) { - assert.expect(14); - class Dog extends Model { @attr() name; @attr() breed; diff --git a/packages/-ember-data/tests/unit/record-arrays/adapter-populated-record-array-test.js b/packages/-ember-data/tests/unit/record-arrays/adapter-populated-record-array-test.js index 90493ff4c9f..7f1afeafdb2 100644 --- a/packages/-ember-data/tests/unit/record-arrays/adapter-populated-record-array-test.js +++ b/packages/-ember-data/tests/unit/record-arrays/adapter-populated-record-array-test.js @@ -155,10 +155,13 @@ module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedR assert.equal(recordArray.get('meta').bar, 2); }); assert.equal(didLoad, 1, 'didLoad event should have fired once'); + assert.expectDeprecation({ + id: 'ember-data:evented-api-usage', + }); }); test('change events when receiving a new query payload', function(assert) { - assert.expect(37); + assert.expect(38); let arrayDidChange = 0; let contentDidChange = 0; @@ -276,5 +279,9 @@ module('unit/record-arrays/adapter-populated-record-array - DS.AdapterPopulatedR assert.equal(contentDidChange, 0, 'recordArray.content should not have changed'); assert.deepEqual(recordArray.map(x => x.name), ['Scumbag Penner']); + assert.expectDeprecation({ + id: 'ember-data:evented-api-usage', + count: 1, + }); }); }); diff --git a/packages/-ember-data/tests/unit/store/adapter-interop-test.js b/packages/-ember-data/tests/unit/store/adapter-interop-test.js index 39e162990f8..c64d8d86ee2 100644 --- a/packages/-ember-data/tests/unit/store/adapter-interop-test.js +++ b/packages/-ember-data/tests/unit/store/adapter-interop-test.js @@ -13,32 +13,47 @@ import JSONSerializer from '@ember-data/serializer/json'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import RESTAdapter from '@ember-data/adapter/rest'; import Store from '@ember-data/store'; +import { deprecatedTest } from '../../helpers/deprecated-test'; module('unit/store/adapter-interop - Store working with a Adapter', function(hooks) { setupTest(hooks); - test('Adapter can be set as a name', function(assert) { - this.owner.register('service:store', Store.extend({ adapter: 'application' })); + deprecatedTest( + 'Adapter can be set as a name', + { + id: 'ember-data:default-adapter', + until: '4.0', + }, + function(assert) { + this.owner.register('service:store', Store.extend({ adapter: 'application' })); - let store = this.owner.lookup('service:store'); + let store = this.owner.lookup('service:store'); - assert.ok(store.get('defaultAdapter') instanceof RESTAdapter); - }); + assert.ok(store.get('defaultAdapter') instanceof RESTAdapter); + } + ); - testInDebug('Adapter can not be set as an instance', function(assert) { - assert.expect(1); + deprecatedTest( + 'Adapter can not be set as an instance', + { + id: 'ember-data:default-adapter', + until: '4.0', + }, + function(assert) { + assert.expect(1); - const BadStore = Store.extend({ - adapter: Adapter.create(), - }); + const BadStore = Store.extend({ + adapter: Adapter.create(), + }); - const { owner } = this; + const { owner } = this; - owner.unregister('service:store'); - owner.register('service:store', BadStore); - const store = owner.lookup('service:store'); - assert.expectAssertion(() => store.get('defaultAdapter')); - }); + owner.unregister('service:store'); + owner.register('service:store', BadStore); + const store = owner.lookup('service:store'); + assert.expectAssertion(() => store.get('defaultAdapter')); + } + ); test('Calling Store#find invokes its adapter#find', function(assert) { assert.expect(5); diff --git a/packages/-ember-data/tests/unit/store/push-test.js b/packages/-ember-data/tests/unit/store/push-test.js index b5b2736ac31..6dec599199f 100644 --- a/packages/-ember-data/tests/unit/store/push-test.js +++ b/packages/-ember-data/tests/unit/store/push-test.js @@ -147,8 +147,8 @@ module('unit/store/push - DS.Store#push', function(hooks) { }); }); - test(`Calling push triggers 'didLoad' even if the record hasn't been requested from the adapter`, function(assert) { - assert.expect(1); + test(`Calling push triggers 'didLoad' even if the record hasn't been requested from the adapter`, async function(assert) { + assert.expect(2); let didLoad = new EmberPromise((resolve, reject) => { Person.reopen({ @@ -176,7 +176,10 @@ module('unit/store/push - DS.Store#push', function(hooks) { }); }); - return didLoad; + await didLoad; + assert.expectDeprecation({ + id: 'ember-data:record-lifecycle-event-methods', + }); }); test('Calling push with partial records updates just those attributes', function(assert) { diff --git a/packages/-ember-data/tests/unit/store/serializer-for-test.js b/packages/-ember-data/tests/unit/store/serializer-for-test.js index 6847551a163..4c6917415d5 100644 --- a/packages/-ember-data/tests/unit/store/serializer-for-test.js +++ b/packages/-ember-data/tests/unit/store/serializer-for-test.js @@ -43,7 +43,7 @@ module('unit/store/serializer_for - DS.Store#serializerFor', function(hooks) { deprecatedTest( 'Calling serializerFor with a type that has not been registered and in an application that does not have an ApplicationSerializer looks up the default Ember Data serializer', { - id: 'ember-data:default-serializers', + id: 'ember-data:default-serializer', until: '4.0', }, function(assert) { diff --git a/packages/adapter/addon/rest.js b/packages/adapter/addon/rest.js index 4e67d528354..f4428faf141 100644 --- a/packages/adapter/addon/rest.js +++ b/packages/adapter/addon/rest.js @@ -1086,7 +1086,7 @@ const RESTAdapter = Adapter.extend(BuildURLMixin, { let headers = get(this, 'headers'); if (headers !== undefined) { - options.headers = assign({}, options.headers, headers); + options.headers = assign({}, headers, options.headers); } else if (!options.headers) { options.headers = {}; } diff --git a/packages/store/addon/-private/system/model/errors.js b/packages/model/addon/-private/errors.js similarity index 98% rename from packages/store/addon/-private/system/model/errors.js rename to packages/model/addon/-private/errors.js index 4fa216bdc62..1e11a3a757d 100644 --- a/packages/store/addon/-private/system/model/errors.js +++ b/packages/model/addon/-private/errors.js @@ -1,5 +1,5 @@ import { mapBy, not } from '@ember/object/computed'; -import DeprecatedEvent from '../deprecated-evented'; +import { DeprecatedEvented } from '@ember-data/store/-private'; import ArrayProxy from '@ember/array/proxy'; import { get, computed } from '@ember/object'; import { makeArray, A } from '@ember/array'; @@ -82,7 +82,7 @@ import { DEBUG } from '@glimmer/env'; @extends Ember.ArrayProxy @uses Ember.Evented */ -export default ArrayProxy.extend(DeprecatedEvent, { +export default ArrayProxy.extend(DeprecatedEvented, { /** Register with target handler diff --git a/packages/model/addon/-private/index.ts b/packages/model/addon/-private/index.ts index 161b31d72d5..b4a458de87d 100644 --- a/packages/model/addon/-private/index.ts +++ b/packages/model/addon/-private/index.ts @@ -2,3 +2,4 @@ export { default as attr } from './attr'; export { default as belongsTo } from './belongs-to'; export { default as hasMany } from './has-many'; export { default as Model } from './model'; +export { default as Errors } from './errors'; diff --git a/packages/model/addon/-private/model.js b/packages/model/addon/-private/model.js index 4b18dffc8da..8f98ebc26fa 100644 --- a/packages/model/addon/-private/model.js +++ b/packages/model/addon/-private/model.js @@ -9,7 +9,6 @@ import { coerceId, DeprecatedEvented, errorsArrayToHash, - Errors, InternalModel, PromiseObject, recordDataFor, @@ -20,6 +19,7 @@ import { relationshipsDescriptor, RootState, } from '@ember-data/store/-private'; +import Errors from './errors'; const { changeProperties } = Ember; diff --git a/packages/record-data/addon/-private/relationships/state/belongs-to.ts b/packages/record-data/addon/-private/relationships/state/belongs-to.ts index f449a3d8473..b0d6e5bc489 100644 --- a/packages/record-data/addon/-private/relationships/state/belongs-to.ts +++ b/packages/record-data/addon/-private/relationships/state/belongs-to.ts @@ -95,7 +95,6 @@ export default class BelongsToRelationship extends Relationship { this.notifyBelongsToChange(); } } - removeCompletelyFromInverse() { super.removeCompletelyFromInverse(); diff --git a/packages/store/addon/-private/index.ts b/packages/store/addon/-private/index.ts index fa9d2d42b25..91243f2f036 100644 --- a/packages/store/addon/-private/index.ts +++ b/packages/store/addon/-private/index.ts @@ -2,8 +2,6 @@ @module @ember-data/store */ -// // public -export { default as Errors } from './system/model/errors'; export { default as Store } from './system/ds-model-store'; export { recordIdentifierFor } from './system/store/internal-model-factory'; diff --git a/packages/store/addon/-private/system/core-store.ts b/packages/store/addon/-private/system/core-store.ts index 590db4d4cbe..b4cf1b8b3e2 100644 --- a/packages/store/addon/-private/system/core-store.ts +++ b/packages/store/addon/-private/system/core-store.ts @@ -68,7 +68,7 @@ import { DSModel } from '../ts-interfaces/ds-model'; import NotificationManager from './record-notification-manager'; import { AttributesSchema } from '../ts-interfaces/record-data-schemas'; import { SchemaDefinitionService } from '../ts-interfaces/schema-definition-service'; -import ShimModelClass from './model/shim-model-class'; +import ShimModelClass, { getShimClass } from './model/shim-model-class'; import RecordDataRecordWrapper from '../ts-interfaces/record-data-record-wrapper'; import RecordData from '../ts-interfaces/record-data'; import { Dict } from '../ts-interfaces/utils'; @@ -513,7 +513,7 @@ abstract class CoreStore extends Service { assertDestroyedStoreOnly(this, 'modelFor'); } - return new ShimModelClass(this, modelName); + return getShimClass(this, modelName); } // Feature Flagged in DSModelStore diff --git a/packages/store/addon/-private/system/ds-model-store.ts b/packages/store/addon/-private/system/ds-model-store.ts index 59b35d9b3c8..a79406e3213 100644 --- a/packages/store/addon/-private/system/ds-model-store.ts +++ b/packages/store/addon/-private/system/ds-model-store.ts @@ -10,7 +10,7 @@ import { isPresent } from '@ember/utils'; import { deprecate } from '@ember/application/deprecations'; import EmberError from '@ember/error'; import { get } from '@ember/object'; -import ShimModelClass from './model/shim-model-class'; +import ShimModelClass, { getShimClass } from './model/shim-model-class'; import { setOwner, getOwner } from '@ember/application'; import { DSModel } from '../ts-interfaces/ds-model'; import NotificationManager from './record-notification-manager'; @@ -21,6 +21,7 @@ import RecordDataRecordWrapper from '../ts-interfaces/record-data-record-wrapper import { SchemaDefinitionService } from '../ts-interfaces/schema-definition-service'; import { RelationshipsSchema } from '../ts-interfaces/record-data-schemas'; import notifyChanges from './model/notify-changes'; +type DSModelClass = import('@ember-data/model').default; /** The store service contains all of the data for records loaded from the server. @@ -143,7 +144,7 @@ class Store extends CoreStore { @param {String} modelName @return {Model} */ - modelFor(modelName) { + modelFor(modelName: string): ShimModelClass | DSModelClass { if (DEBUG) { assertDestroyedStoreOnly(this, 'modelFor'); } @@ -156,15 +157,18 @@ class Store extends CoreStore { let maybeFactory = this._modelFactoryFor(modelName); // for factorFor factory/class split - let klass = maybeFactory.class ? maybeFactory.class : maybeFactory; - if (!klass.isModel) { - return new ShimModelClass(this, modelName); + let klass = maybeFactory && maybeFactory.class ? maybeFactory.class : maybeFactory; + if (!klass || !klass.isModel) { + if (!CUSTOM_MODEL_CLASS || !this.getSchemaDefinitionService().doesTypeExist(modelName)) { + throw new EmberError(`No model was found for '${modelName}' and no schema handles the type`); + } + return getShimClass(this, modelName); } else { return klass; } } - _modelFactoryFor(modelName) { + _modelFactoryFor(modelName: string): DSModelClass { if (DEBUG) { assertDestroyedStoreOnly(this, '_modelFactoryFor'); } @@ -176,10 +180,6 @@ class Store extends CoreStore { let normalizedModelName = normalizeModelName(modelName); let factory = getModelFactory(this, this._modelFactoryCache, normalizedModelName); - if (factory === null) { - throw new EmberError(`No model was found for '${normalizedModelName}'`); - } - return factory; } diff --git a/packages/store/addon/-private/system/model/internal-model.ts b/packages/store/addon/-private/system/model/internal-model.ts index b26350fcbb0..cf63ac6a80c 100644 --- a/packages/store/addon/-private/system/model/internal-model.ts +++ b/packages/store/addon/-private/system/model/internal-model.ts @@ -758,7 +758,7 @@ export default class InternalModel { loadingPromise = this.store ._findHasManyByJsonApiResource(jsonApi, this, relationshipMeta, options) - .then(initialState => { + .then(() => { // TODO why don't we do this in the store method manyArray.retrieveLatest(); manyArray.set('isLoaded', true); diff --git a/packages/store/addon/-private/system/model/shim-model-class.ts b/packages/store/addon/-private/system/model/shim-model-class.ts index 33147f9cc52..83ad278011e 100644 --- a/packages/store/addon/-private/system/model/shim-model-class.ts +++ b/packages/store/addon/-private/system/model/shim-model-class.ts @@ -1,5 +1,24 @@ import CoreStore from '../core-store'; import { RelationshipSchema, AttributeSchema } from '../../ts-interfaces/record-data-schemas'; +import { Dict } from '../../ts-interfaces/utils'; + +const AvailableShims = new WeakMap>(); + +export function getShimClass(store: CoreStore, modelName: string): ShimModelClass { + let shims = AvailableShims.get(store); + + if (shims === undefined) { + shims = Object.create(null) as Dict; + AvailableShims.set(store, shims); + } + + let shim = shims[modelName]; + if (shim === undefined) { + shim = shims[modelName] = new ShimModelClass(store, modelName); + } + + return shim; +} // Mimics the static apis of DSModel export default class ShimModelClass { diff --git a/packages/store/addon/-private/system/schema-definition-service.ts b/packages/store/addon/-private/system/schema-definition-service.ts index f1a8a92d5a8..a7ec125e67b 100644 --- a/packages/store/addon/-private/system/schema-definition-service.ts +++ b/packages/store/addon/-private/system/schema-definition-service.ts @@ -5,6 +5,8 @@ import { getOwner } from '@ember/application'; import normalizeModelName from './normalize-model-name'; import { RelationshipsSchema, AttributesSchema } from '../ts-interfaces/record-data-schemas'; import require, { has } from 'require'; +import CoreStore from './core-store'; +type Model = import('@ember-data/model').default; const HAS_MODEL_PACKAGE = has('@ember-data/model'); let _Model; @@ -80,7 +82,7 @@ export class DSModelSchemaDefinitionService { * @param normalizedModelName already normalized modelName * @return {*} */ -export function getModelFactory(store, cache, normalizedModelName) { +export function getModelFactory(store: CoreStore, cache, normalizedModelName: string): Model | null { let factory = cache[normalizedModelName]; if (!factory) { @@ -97,9 +99,7 @@ export function getModelFactory(store, cache, normalizedModelName) { } let klass = factory.class; - // assert(`'${inspect(klass)}' does not appear to be an ember-data model`, klass.isModel); - // TODO: deprecate this if (klass.isModel) { let hasOwnModelNameSet = klass.modelName && Object.prototype.hasOwnProperty.call(klass, 'modelName'); if (!hasOwnModelNameSet) { diff --git a/packages/-fastboot-test-app/app/routes/person/new.js b/packages/unpublished-fastboot-test-app/app/routes/person/new.js similarity index 100% rename from packages/-fastboot-test-app/app/routes/person/new.js rename to packages/unpublished-fastboot-test-app/app/routes/person/new.js diff --git a/packages/-fastboot-test-app/app/templates/person/new.hbs b/packages/unpublished-fastboot-test-app/app/templates/person/new.hbs similarity index 100% rename from packages/-fastboot-test-app/app/templates/person/new.hbs rename to packages/unpublished-fastboot-test-app/app/templates/person/new.hbs diff --git a/packages/-fastboot-test-app/tests/fastboot/person/new-test.js b/packages/unpublished-fastboot-test-app/tests/fastboot/person/new-test.js similarity index 100% rename from packages/-fastboot-test-app/tests/fastboot/person/new-test.js rename to packages/unpublished-fastboot-test-app/tests/fastboot/person/new-test.js diff --git a/packages/unpublished-model-encapsulation-test-app/app/app.js b/packages/unpublished-model-encapsulation-test-app/app/app.js index f08aaaf0304..bfd9f02e6ae 100644 --- a/packages/unpublished-model-encapsulation-test-app/app/app.js +++ b/packages/unpublished-model-encapsulation-test-app/app/app.js @@ -3,6 +3,13 @@ import Resolver from './resolver'; import loadInitializers from 'ember-load-initializers'; import config from './config/environment'; +window.EmberDataENV = { + ENABLE_OPTIONAL_FEATURES: true, + FEATURES: { + CUSTOM_MODEL_CLASS: true, + }, +}; + const App = Application.extend({ modulePrefix: config.modulePrefix, podModulePrefix: config.podModulePrefix, diff --git a/packages/unpublished-model-encapsulation-test-app/tests/integration/model-for-test.js b/packages/unpublished-model-encapsulation-test-app/tests/integration/model-for-test.js new file mode 100644 index 00000000000..e65abd23e79 --- /dev/null +++ b/packages/unpublished-model-encapsulation-test-app/tests/integration/model-for-test.js @@ -0,0 +1,97 @@ +import Store from '@ember-data/store'; +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('modelFor without @ember-data/model', function(hooks) { + setupTest(hooks); + + test('We can call modelFor', function(assert) { + this.owner.register( + 'service:store', + class TestStore extends Store { + instantiateRecord() { + return { + id: '1', + type: 'user', + name: 'Chris Thoburn', + }; + } + teardownRecord() { + return; + } + } + ); + const store = this.owner.lookup('service:store'); + store.registerSchemaDefinitionService({ + attributesDefinitionFor(identifier) { + return { + name: { + name: 'name', + }, + }; + }, + relationshipsDefinitionFor(identifier) { + return {}; + }, + doesTypeExist(type) { + return type === 'user'; + }, + }); + + try { + store.modelFor('user'); + assert.ok(true, 'We should not throw an eror when schema is available'); + } catch (e) { + assert.ok(false, `We threw an unexpected error when schema is available: ${e.message}`); + } + + try { + store.modelFor('person'); + assert.ok(false, 'We should throw an eror when no schema is available'); + } catch (e) { + assert.strictEqual( + e.message, + "No model was found for 'person' and no schema handles the type", + 'We throw an error when no schema is available' + ); + } + }); + + test('modelFor returns a stable reference', function(assert) { + this.owner.register( + 'service:store', + class TestStore extends Store { + instantiateRecord() { + return { + id: '1', + type: 'user', + name: 'Chris Thoburn', + }; + } + teardownRecord() { + return; + } + } + ); + const store = this.owner.lookup('service:store'); + store.registerSchemaDefinitionService({ + attributesDefinitionFor(identifier) { + return { + name: { + name: 'name', + }, + }; + }, + relationshipsDefinitionFor(identifier) { + return {}; + }, + doesTypeExist(type) { + return type === 'user'; + }, + }); + + const ShimUser1 = store.modelFor('user'); + const ShimUser2 = store.modelFor('user'); + assert.strictEqual(ShimUser1, ShimUser2, 'Repeat modelFor calls return the same shim'); + }); +}); diff --git a/packages/unpublished-relationship-performance-test-app/PERFORMANCE_BENCHMARKING.md b/packages/unpublished-relationship-performance-test-app/PERFORMANCE_BENCHMARKING.md index 370cae484cb..2b3849a964a 100644 --- a/packages/unpublished-relationship-performance-test-app/PERFORMANCE_BENCHMARKING.md +++ b/packages/unpublished-relationship-performance-test-app/PERFORMANCE_BENCHMARKING.md @@ -9,5 +9,5 @@ A HAR file has already been created for this app from a production build. It can Next, run the following from the root of this repository: ``` -./bin/relationship-performance-check +./bin/relationship-performance-tracking/src/generate-analysis.sh ``` diff --git a/packages/unpublished-relationship-performance-test-app/tracerbench-results/.gitignore b/packages/unpublished-relationship-performance-test-app/tracerbench-results/.gitignore index 94a2dd146a2..f59ec20aabf 100644 --- a/packages/unpublished-relationship-performance-test-app/tracerbench-results/.gitignore +++ b/packages/unpublished-relationship-performance-test-app/tracerbench-results/.gitignore @@ -1 +1 @@ -*.json \ No newline at end of file +* \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f538c45824c..24558dd3d82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,6 +39,16 @@ lodash "^4.17.13" source-map "^0.5.0" +"@babel/generator@^7.7.0": + version "7.7.0" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.7.0.tgz#c6d4d1f7a0d6e139cbd01aca73170b0bff5425b4" + integrity sha512-1wdJ6UxHyL1XoJQ119JmvuRX27LRih7iYStMPZOWAjQqeAabFg3dYXKMpgihma+to+0ADsTVVt6oRyUxWZw6Mw== + dependencies: + "@babel/types" "^7.7.0" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + "@babel/helper-annotate-as-pure@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" @@ -63,17 +73,17 @@ "@babel/traverse" "^7.4.4" "@babel/types" "^7.4.4" -"@babel/helper-create-class-features-plugin@^7.5.5", "@babel/helper-create-class-features-plugin@^7.6.0": - version "7.6.0" - resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.6.0.tgz#769711acca889be371e9bc2eb68641d55218021f" - integrity sha512-O1QWBko4fzGju6VoVvrZg0RROCVifcLxiApnGP3OWfWzvxRZFCoBD81K5ur5e3bVY2Vf/5rIJm8cqPKn8HUJng== +"@babel/helper-create-class-features-plugin@^7.5.5", "@babel/helper-create-class-features-plugin@^7.6.0", "@babel/helper-create-class-features-plugin@^7.7.0": + version "7.7.0" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.7.0.tgz#bcdc223abbfdd386f94196ae2544987f8df775e8" + integrity sha512-MZiB5qvTWoyiFOgootmRSDV1udjIqJW/8lmxgzKq6oDqxdmHUjeP2ZUOmgHdYjmUVNABqRrHjYAYRvj8Eox/UA== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-member-expression-to-functions" "^7.5.5" - "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/helper-function-name" "^7.7.0" + "@babel/helper-member-expression-to-functions" "^7.7.0" + "@babel/helper-optimise-call-expression" "^7.7.0" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.5.5" - "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/helper-replace-supers" "^7.7.0" + "@babel/helper-split-export-declaration" "^7.7.0" "@babel/helper-define-map@^7.5.5": version "7.5.5" @@ -101,6 +111,15 @@ "@babel/template" "^7.1.0" "@babel/types" "^7.0.0" +"@babel/helper-function-name@^7.7.0": + version "7.7.0" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz#44a5ad151cfff8ed2599c91682dda2ec2c8430a3" + integrity sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q== + dependencies: + "@babel/helper-get-function-arity" "^7.7.0" + "@babel/template" "^7.7.0" + "@babel/types" "^7.7.0" + "@babel/helper-get-function-arity@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" @@ -108,6 +127,13 @@ dependencies: "@babel/types" "^7.0.0" +"@babel/helper-get-function-arity@^7.7.0": + version "7.7.0" + resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz#c604886bc97287a1d1398092bc666bc3d7d7aa2d" + integrity sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw== + dependencies: + "@babel/types" "^7.7.0" + "@babel/helper-hoist-variables@^7.4.4": version "7.4.4" resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz#0298b5f25c8c09c53102d52ac4a98f773eb2850a" @@ -122,6 +148,13 @@ dependencies: "@babel/types" "^7.5.5" +"@babel/helper-member-expression-to-functions@^7.7.0": + version "7.7.0" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.0.tgz#472b93003a57071f95a541ea6c2b098398bcad8a" + integrity sha512-QaCZLO2RtBcmvO/ekOLp8p7R5X2JriKRizeDpm5ChATAFWrrYDcDxPuCIBXKyBjY+i1vYSdcUTMIb8psfxHDPA== + dependencies: + "@babel/types" "^7.7.0" + "@babel/helper-module-imports@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d" @@ -148,6 +181,13 @@ dependencies: "@babel/types" "^7.0.0" +"@babel/helper-optimise-call-expression@^7.7.0": + version "7.7.0" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.0.tgz#4f66a216116a66164135dc618c5d8b7a959f9365" + integrity sha512-48TeqmbazjNU/65niiiJIJRc5JozB8acui1OS7bSd6PgxfuovWsvjfWSzlgx+gPFdVveNzUdpdIg5l56Pl5jqg== + dependencies: + "@babel/types" "^7.7.0" + "@babel/helper-plugin-utils@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" @@ -181,6 +221,16 @@ "@babel/traverse" "^7.5.5" "@babel/types" "^7.5.5" +"@babel/helper-replace-supers@^7.7.0": + version "7.7.0" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.7.0.tgz#d5365c8667fe7cbd13b8ddddceb9bd7f2b387512" + integrity sha512-5ALYEul5V8xNdxEeWvRsBzLMxQksT7MaStpxjJf9KsnLxpAKBtfw5NeMKZJSYDa0lKdOcy0g+JT/f5mPSulUgg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.7.0" + "@babel/helper-optimise-call-expression" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + "@babel/helper-simple-access@^7.1.0": version "7.1.0" resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c" @@ -196,6 +246,13 @@ dependencies: "@babel/types" "^7.4.4" +"@babel/helper-split-export-declaration@^7.7.0": + version "7.7.0" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz#1365e74ea6c614deeb56ebffabd71006a0eb2300" + integrity sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA== + dependencies: + "@babel/types" "^7.7.0" + "@babel/helper-wrap-function@^7.1.0": version "7.2.0" resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz#c4e0012445769e2815b55296ead43a958549f6fa" @@ -229,6 +286,11 @@ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81" integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A== +"@babel/parser@^7.7.0": + version "7.7.0" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.7.0.tgz#232618f6e8947bc54b407fa1f1c91a22758e7159" + integrity sha512-GqL+Z0d7B7ADlQBMXlJgvXEbtt5qlqd1YQ5fr12hTSfh7O/vgrEIvJxU2e7aSVrEUn75zTZ6Nd0s8tthrlZnrQ== + "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" @@ -599,12 +661,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-typescript@^7.6.3": - version "7.6.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.6.3.tgz#dddb50cf3b8b2ef70b22e5326e9a91f05a1db13b" - integrity sha512-aiWINBrPMSC3xTXRNM/dfmyYuPNKY/aexYqBgh0HBI5Y+WO5oRAqW/oROYeYHrF4Zw12r9rK4fMk/ZlAmqx/FQ== +"@babel/plugin-transform-typescript@^7.7.0": + version "7.7.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.7.0.tgz#182be03fa8bd2ffd0629791a1eaa4373b7589d38" + integrity sha512-y3KYbcfKe+8ziRXiGhhnGrVysDBo5+aJdB+x8sanM0K41cnmK7Q5vBlQLMbOnW/HPjLG9bg7dLgYDQZZG9T09g== dependencies: - "@babel/helper-create-class-features-plugin" "^7.6.0" + "@babel/helper-create-class-features-plugin" "^7.7.0" "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-typescript" "^7.2.0" @@ -714,6 +776,15 @@ "@babel/parser" "^7.6.0" "@babel/types" "^7.6.0" +"@babel/template@^7.7.0": + version "7.7.0" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz#4fadc1b8e734d97f56de39c77de76f2562e597d0" + integrity sha512-OKcwSYOW1mhWbnTBgQY5lvg1Fxg+VyfQGjcBduZFljfc044J5iDlnDSfhQ867O17XHiSCxYHUxHg2b7ryitbUQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/types" "^7.7.0" + "@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.6", "@babel/traverse@^7.2.4", "@babel/traverse@^7.3.4", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5", "@babel/traverse@^7.5.5", "@babel/traverse@^7.6.2", "@babel/traverse@^7.6.3": version "7.6.3" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9" @@ -729,6 +800,21 @@ globals "^11.1.0" lodash "^4.17.13" +"@babel/traverse@^7.7.0": + version "7.7.0" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.0.tgz#9f5744346b8d10097fd2ec2eeffcaf19813cbfaf" + integrity sha512-ea/3wRZc//e/uwCpuBX2itrhI0U9l7+FsrKWyKGNyvWbuMcCG7ATKY2VI4wlg2b2TA39HHwIxnvmXvtiKsyn7w== + dependencies: + "@babel/code-frame" "^7.5.5" + "@babel/generator" "^7.7.0" + "@babel/helper-function-name" "^7.7.0" + "@babel/helper-split-export-declaration" "^7.7.0" + "@babel/parser" "^7.7.0" + "@babel/types" "^7.7.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + "@babel/types@^7.0.0", "@babel/types@^7.1.6", "@babel/types@^7.2.0", "@babel/types@^7.3.2", "@babel/types@^7.3.4", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.6.0", "@babel/types@^7.6.3": version "7.6.3" resolved "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09" @@ -738,6 +824,15 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@babel/types@^7.7.0": + version "7.7.1" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.7.1.tgz#8b08ea368f2baff236613512cf67109e76285827" + integrity sha512-kN/XdANDab9x1z5gcjDc9ePpxexkt+1EQ2MQUiM4XnMvQfvp87/+6kY4Ko2maLXH+tei/DgJ/ybFITeqqRwDiA== + dependencies: + esutils "^2.0.2" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.3" resolved "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" @@ -2087,46 +2182,47 @@ resolved "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz#1073c4bc824754ae3d10cfab88ab0237ba964e4d" integrity sha1-EHPEvIJHVK49EM+riKsCN7qWTk0= -"@typescript-eslint/eslint-plugin@^2.6.0": - version "2.6.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.6.0.tgz#e82ed43fc4527b21bfe35c20a2d6e4ed49fc7957" - integrity sha512-iCcXREU4RciLmLniwKLRPCOFVXrkF7z27XuHq5DrykpREv/mz6ztKAyLg2fdkM0hQC7659p5ZF5uStH7uzAJ/w== +"@typescript-eslint/eslint-plugin@^2.6.1": + version "2.6.1" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.6.1.tgz#e34972a24f8aba0861f9ccf7130acd74fd11e079" + integrity sha512-Z0rddsGqioKbvqfohg7BwkFC3PuNLsB+GE9QkFza7tiDzuHoy0y823Y+oGNDzxNZrYyLjqkZtCTl4vCqOmEN4g== dependencies: - "@typescript-eslint/experimental-utils" "2.6.0" + "@typescript-eslint/experimental-utils" "2.6.1" eslint-utils "^1.4.2" functional-red-black-tree "^1.0.1" regexpp "^2.0.1" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.6.0": - version "2.6.0" - resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.6.0.tgz#ed70bef72822bff54031ff0615fc888b9e2b6e8a" - integrity sha512-34BAFpNOwHXeqT+AvdalLxOvcPYnCxA5JGmBAFL64RGMdP0u65rXjii7l/nwpgk5aLEE1LaqF+SsCU0/Cb64xA== +"@typescript-eslint/experimental-utils@2.6.1": + version "2.6.1" + resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.6.1.tgz#eddaca17a399ebf93a8628923233b4f93793acfd" + integrity sha512-EVrrUhl5yBt7fC7c62lWmriq4MIc49zpN3JmrKqfiFXPXCM5ErfEcZYfKOhZXkW6MBjFcJ5kGZqu1b+lyyExUw== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.6.0" + "@typescript-eslint/typescript-estree" "2.6.1" eslint-scope "^5.0.0" -"@typescript-eslint/parser@^2.6.0": - version "2.6.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.6.0.tgz#5106295c6a7056287b4719e24aae8d6293d5af49" - integrity sha512-AvLejMmkcjRTJ2KD72v565W4slSrrzUIzkReu1JN34b8JnsEsxx7S9Xx/qXEuMQas0mkdUfETr0j3zOhq2DIqQ== +"@typescript-eslint/parser@^2.6.1": + version "2.6.1" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.6.1.tgz#3c00116baa0d696bc334ca18ac5286b34793993c" + integrity sha512-PDPkUkZ4c7yA+FWqigjwf3ngPUgoLaGjMlFh6TRtbjhqxFBnkElDfckSjm98q9cMr4xRzZ15VrS/xKm6QHYf0w== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.6.0" - "@typescript-eslint/typescript-estree" "2.6.0" + "@typescript-eslint/experimental-utils" "2.6.1" + "@typescript-eslint/typescript-estree" "2.6.1" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@2.6.0": - version "2.6.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.6.0.tgz#d3e9d8e001492e2b9124c4d4bd4e7f03c0fd7254" - integrity sha512-A3lSBVIdj2Gp0lFEL6in2eSPqJ33uAc3Ko+Y4brhjkxzjbzLnwBH22CwsW2sCo+iwogfIyvb56/AJri15H0u5Q== +"@typescript-eslint/typescript-estree@2.6.1": + version "2.6.1" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.6.1.tgz#fb363dd4ca23384745c5ea4b7f4c867432b00d31" + integrity sha512-+sTnssW6bcbDZKE8Ce7VV6LdzkQz2Bxk7jzk1J8H1rovoTxnm6iXvYIyncvNsaB/kBCOM63j/LNJfm27bNdUoA== dependencies: debug "^4.1.1" glob "^7.1.4" is-glob "^4.0.1" lodash.unescape "4.0.1" semver "^6.3.0" + tsutils "^3.17.1" "@webassemblyjs/ast@1.7.11": version "1.7.11" @@ -2271,11 +2367,6 @@ "@webassemblyjs/wast-parser" "1.7.11" "@xtuc/long" "4.2.1" -"@xg-wang/whatwg-fetch@^3.0.0": - version "3.0.0" - resolved "https://registry.npmjs.org/@xg-wang/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#f7b222c012a238e7d6e89ed3d72a1e0edb58453d" - integrity sha512-ULtqA6L75RLzTNW68IiOja0XYv4Ebc3OGMzfia1xxSEMpD0mk/pMvkQX0vbCFyQmKc5xGp80Ms2WiSlXLh8hbA== - "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -6108,17 +6199,18 @@ ember-cli-preprocess-registry@^3.1.2, ember-cli-preprocess-registry@^3.3.0: debug "^3.0.1" process-relative-require "^1.0.0" -ember-cli-pretender@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/ember-cli-pretender/-/ember-cli-pretender-3.1.1.tgz#289c41683de266fec8bfaf5b7b7f6026aaefc8cf" - integrity sha512-RGGj9la0138bgHUxyaGDHCZydmdpW+BFN9v0vMBzNPeXsaexCZotaFTIZDCNcKWPx8jtRHR8AXf318VRGXLJsw== +ember-cli-pretender@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/ember-cli-pretender/-/ember-cli-pretender-3.2.0.tgz#8d91a20bb5f57092a328317591ec1dd622fcadf1" + integrity sha512-3q7a0bjbHAevWaQQE447McT1neQIMgBjllfbOVX27D1qQqCZ5h6qMsRe7fU44B6/UQcCcghpbNoeEcNcI56wOg== dependencies: abortcontroller-polyfill "^1.1.9" broccoli-funnel "^2.0.1" broccoli-merge-trees "^3.0.0" + chalk "^2.4.2" ember-cli-babel "^6.6.0" fake-xml-http-request "^2.0.0" - pretender "^2.1.0" + pretender "^3.0.1" route-recognizer "^0.3.3" whatwg-fetch "^3.0.0" @@ -6263,10 +6355,10 @@ ember-cli-version-checker@^3.0.0, ember-cli-version-checker@^3.0.1, ember-cli-ve resolve-package-path "^1.2.6" semver "^5.6.0" -ember-cli-yuidoc@^0.8.8: - version "0.8.8" - resolved "https://registry.npmjs.org/ember-cli-yuidoc/-/ember-cli-yuidoc-0.8.8.tgz#3858baaf85388a976024f9de40f1075fea58f606" - integrity sha1-OFi6r4U4ipdgJPneQPEHX+pY9gY= +ember-cli-yuidoc@^0.9.1: + version "0.9.1" + resolved "https://registry.npmjs.org/ember-cli-yuidoc/-/ember-cli-yuidoc-0.9.1.tgz#bc2171eb160b02ad5f4078e83e3648b95628ced9" + integrity sha512-4pb3OKXhHCeUux6a7SDKziLDWdDciJwzmUld3Fumt60RLcH/nIk5lPdI0o+UXJ9NfP+WcSvvpWWroFmWqWAWWA== dependencies: broccoli-caching-writer "~2.0.4" broccoli-merge-trees "^1.1.1" @@ -11186,14 +11278,14 @@ prepend-http@^2.0.0: resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -pretender@^2.1.0: - version "2.1.1" - resolved "https://registry.npmjs.org/pretender/-/pretender-2.1.1.tgz#5085f0a1272c31d5b57c488386f69e6ca207cb35" - integrity sha512-IkidsJzaroAanw3I43tKCFm2xCpurkQr9aPXv5/jpN+LfCwDaeI8rngVWtQZTx4qqbhc5zJspnLHJ4N/25KvDQ== +pretender@^3.0.1: + version "3.0.4" + resolved "https://registry.npmjs.org/pretender/-/pretender-3.0.4.tgz#e99fb39ae2b67ec7c1a2d0fc8f0cc63d4a8e9aff" + integrity sha512-l8geJ6tbBAe9Dl7a5aMKi5nYTabTci/wYBGA1MVVYfq1wLpBe+KYZM3YW+IvXuH4L/Vt1+zudmyigzFAuoWdDg== dependencies: - "@xg-wang/whatwg-fetch" "^3.0.0" fake-xml-http-request "^2.0.0" route-recognizer "^0.3.3" + whatwg-fetch "^3.0.0" prettier-linter-helpers@^1.0.0: version "1.0.0" @@ -14188,3 +14280,8 @@ yuidocjs@^0.10.0: minimatch "^3.0.2" rimraf "^2.4.1" yui "^3.18.1" + +zlib@1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0" + integrity sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA=