Remove Karma and anything related

This commit is contained in:
Vitaly Slobodin 2021-09-03 14:50:29 +00:00 committed by Natalia Tepluhina
parent 6df7c2bf66
commit bd710a55b6
33 changed files with 71 additions and 2549 deletions

View file

@ -36,6 +36,5 @@ exclude_paths:
- webpack-report/
- log/
- backups/
- coverage-javascript/
- plugins/
- file_hooks/

View file

@ -2,7 +2,6 @@
/builds/
/coverage/
/coverage-frontend/
/coverage-javascript/
/node_modules/
/public/
/tmp/

1
.gitignore vendored
View file

@ -50,7 +50,6 @@ eslint-report.html
/config/sidekiq.yml
/config/registry.key
/coverage/*
/coverage-javascript/
/db/*.sqlite3
/db/*.sqlite3-journal
/db/data.yml

View file

@ -152,8 +152,6 @@
*.js @gitlab-org/maintainers/frontend
/app/assets/ @gitlab-org/maintainers/frontend
/ee/app/assets/ @gitlab-org/maintainers/frontend
/spec/javascripts/ @gitlab-org/maintainers/frontend
/ee/spec/javascripts/ @gitlab-org/maintainers/frontend
/spec/frontend/ @gitlab-org/maintainers/frontend
/ee/spec/frontend/ @gitlab-org/maintainers/frontend
/spec/frontend_integration/ @gitlab-org/maintainers/frontend

View file

@ -184,41 +184,6 @@ eslint-as-if-foss:
- *yarn-install
- run_timed_command "yarn run lint:eslint:all"
.karma-base:
extends: .frontend-test-base
script:
- export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log
- *yarn-install
- run_timed_command "yarn karma"
karma:
extends:
- .karma-base
- .frontend:rules:default-frontend-jobs
needs:
- job: "rspec frontend_fixture"
- job: "rspec-ee frontend_fixture"
optional: true
coverage: '/^Statements *: (\d+\.\d+%)/'
artifacts:
name: coverage-javascript
expire_in: 31d
when: always
paths:
- chrome_debug.log
- coverage-javascript/
- tmp/tests/frontend/
reports:
junit: junit_karma.xml
cobertura: coverage-javascript/cobertura-coverage.xml
karma-as-if-foss:
extends:
- .karma-base
- .frontend:rules:default-frontend-jobs-as-if-foss
- .as-if-foss
needs: ["rspec frontend_fixture as-if-foss"]
.jest-base:
extends: .frontend-test-base
script:

View file

@ -12,7 +12,6 @@ pages:
needs:
- job: "rspec:coverage"
- job: "coverage-frontend"
- job: "karma"
- job: "compile-production-assets"
- job: "compile-storybook"
# `update-tests-metadata` only runs on GitLab.com's EE schedules pipelines
@ -27,7 +26,6 @@ pages:
- mkdir -p public/$(dirname "$KNAPSACK_RSPEC_SUITE_REPORT_PATH") public/$(dirname "$FLAKY_RSPEC_SUITE_REPORT_PATH") public/$(dirname "$RSPEC_PACKED_TESTS_MAPPING_PATH")
- mv coverage/ public/coverage-ruby/ || true
- mv coverage-frontend/ public/coverage-frontend/ || true
- mv coverage-javascript/ public/coverage-javascript/ || true
- mv storybook/public public/storybook || true
- cp .public/assets/application-*.css public/application.css || true
- mv $KNAPSACK_RSPEC_SUITE_REPORT_PATH public/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || true

View file

@ -564,7 +564,7 @@ export const addSelectOnFocusBehaviour = (selector = '.js-select-on-focus') => {
*
* Eg; roundOffFloat(3.141592, 3) = 3.142
*
* Refer to spec/javascripts/lib/utils/common_utils_spec.js for
* Refer to spec/frontend/lib/utils/common_utils_spec.js for
* more supported examples.
*
* @param {Float} number
@ -581,7 +581,7 @@ export const roundOffFloat = (number, precision = 0) => {
*
* Eg; roundToNearestHalf(3.141592) = 3, roundToNearestHalf(3.41592) = 3.5
*
* Refer to spec/javascripts/lib/utils/common_utils_spec.js for
* Refer to spec/frontend/lib/utils/common_utils_spec.js for
* more supported examples.
*
* @param {Float} number
@ -595,7 +595,7 @@ export const roundToNearestHalf = (num) => Math.round(num * 2).toFixed() / 2;
*
* Eg; roundDownFloat(3.141592, 3) = 3.141
*
* Refer to spec/javascripts/lib/utils/common_utils_spec.js for
* Refer to spec/frontend/lib/utils/common_utils_spec.js for
* more supported examples.
*
* @param {Float} number
@ -645,7 +645,7 @@ export const NavigationType = {
* matched with our query.
*
* You can learn more about behaviour of this method by referring to tests
* within `spec/javascripts/lib/utils/common_utils_spec.js`.
* within `spec/frontend/lib/utils/common_utils_spec.js`.
*
* @param {string} query String to search for
* @param {object} searchSpace Object containing properties to search in for `query`

View file

@ -31,7 +31,7 @@ if (BABEL_ENV === 'coverage') {
plugins.push([
'babel-plugin-istanbul',
{
exclude: ['spec/javascripts/**/*', 'app/assets/javascripts/locale/**/app.js'],
exclude: ['app/assets/javascripts/locale/**/app.js'],
},
]);
}

View file

@ -1,203 +0,0 @@
/* eslint-disable no-inner-declarations, no-param-reassign */
const path = require('path');
const chalk = require('chalk');
const argumentsParser = require('commander');
const glob = require('glob');
const webpack = require('webpack');
const IS_EE = require('./helpers/is_ee_env');
const webpackConfig = require('./webpack.config');
const ROOT_PATH = path.resolve(__dirname, '..');
const SPECS_PATH = /^(?:\.[\\/])?(ee[\\/])?spec[\\/]javascripts[\\/]/;
function exitError(message) {
console.error(chalk.red(`\nError: ${message}\n`));
process.exit(1);
}
function exitWarn(message) {
console.error(chalk.yellow(`\nWarn: ${message}\n`));
process.exit(0);
}
function exit(message, isError = true) {
const fn = isError ? exitError : exitWarn;
fn(message);
}
// disable problematic options
webpackConfig.entry = undefined;
webpackConfig.mode = 'development';
webpackConfig.optimization.nodeEnv = false;
webpackConfig.optimization.runtimeChunk = false;
webpackConfig.optimization.splitChunks = false;
// use quicker sourcemap option
webpackConfig.devtool = 'cheap-inline-source-map';
// set BABEL_ENV to indicate when we're running code coverage
webpackConfig.plugins.push(
new webpack.DefinePlugin({
'process.env.BABEL_ENV': JSON.stringify(process.env.BABEL_ENV || process.env.NODE_ENV || null),
}),
);
const options = argumentsParser
.option('--no-fail-on-empty-test-suite')
.option(
'-f, --filter-spec [filter]',
'Filter run spec files by path. Multiple filters are like a logical OR.',
(filter, memo) => {
memo.push(filter, filter.replace(/\/?$/, '/**/*.js'));
return memo;
},
[],
)
.parse(process.argv);
const specFilters = options.filterSpec;
const createContext = (specFiles, regex, suffix) => {
const newContext = specFiles.reduce((context, file) => {
const relativePath = file.replace(SPECS_PATH, '');
context[file] = `./${relativePath}`;
return context;
}, {});
webpackConfig.plugins.push(
new webpack.ContextReplacementPlugin(regex, path.join(ROOT_PATH, suffix), newContext),
);
};
if (specFilters.length) {
// resolve filters
let filteredSpecFiles = specFilters.map((filter) =>
glob
.sync(filter, {
root: ROOT_PATH,
matchBase: true,
})
.filter((filePath) => filePath.endsWith('spec.js')),
);
// flatten
filteredSpecFiles = Array.prototype.concat.apply([], filteredSpecFiles);
// remove duplicates
filteredSpecFiles = [...new Set(filteredSpecFiles)];
if (filteredSpecFiles.length < 1) {
const isError = options.failOnEmptyTestSuite;
exit('Your filter did not match any test files.', isError);
}
if (!filteredSpecFiles.every((file) => SPECS_PATH.test(file))) {
exitError('Test files must be located within /spec/javascripts.');
}
const CE_FILES = filteredSpecFiles.filter((file) => !file.startsWith('ee'));
createContext(CE_FILES, /[^e]{2}[\\/]spec[\\/]javascripts$/, 'spec/javascripts');
const EE_FILES = filteredSpecFiles.filter((file) => file.startsWith('ee'));
createContext(EE_FILES, /ee[\\/]spec[\\/]javascripts$/, 'ee/spec/javascripts');
}
// Karma configuration
module.exports = (config) => {
process.env.TZ = 'Etc/UTC';
const fixturesPath = `tmp/tests/frontend/fixtures${IS_EE ? '-ee' : ''}`;
const staticFixturesPath = 'spec/frontend/fixtures/static';
const karmaConfig = {
basePath: ROOT_PATH,
browsers: ['ChromeHeadlessCustom'],
client: {
color: !process.env.CI,
},
customLaunchers: {
ChromeHeadlessCustom: {
base: 'ChromeHeadless',
displayName: 'Chrome',
flags: [
// chrome cannot run in sandboxed mode inside a docker container unless it is run with
// escalated kernel privileges (e.g. docker run --cap-add=CAP_SYS_ADMIN)
'--no-sandbox',
// https://bugs.chromium.org/p/chromedriver/issues/detail?id=2870
'--enable-features=NetworkService,NetworkServiceInProcess',
],
},
},
frameworks: ['jasmine'],
files: [
{ pattern: 'spec/javascripts/test_bundle.js', watched: false },
{ pattern: `${fixturesPath}/**/*`, included: false },
{ pattern: `${staticFixturesPath}/**/*`, included: false },
],
proxies: {
'/fixtures/': `/base/${fixturesPath}/`,
'/fixtures/static/': `/base/${staticFixturesPath}/`,
},
preprocessors: {
'spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
'ee/spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
},
reporters: ['mocha'],
webpack: webpackConfig,
webpackMiddleware: { stats: 'errors-only' },
plugins: [
'karma-chrome-launcher',
'karma-coverage-istanbul-reporter',
'karma-jasmine',
'karma-junit-reporter',
'karma-mocha-reporter',
'karma-sourcemap-loader',
'karma-webpack',
],
};
if (process.env.CI) {
karmaConfig.reporters.push('junit');
karmaConfig.junitReporter = {
outputFile: 'junit_karma.xml',
useBrowserName: false,
};
} else {
// ignore 404s in local environment because we are not fixing them and they bloat the log
function ignore404() {
return (request, response /* next */) => {
response.writeHead(404);
return response.end('NOT FOUND');
};
}
karmaConfig.middleware = ['ignore-404'];
karmaConfig.plugins.push({
'middleware:ignore-404': ['factory', ignore404],
});
}
if (process.env.BABEL_ENV === 'coverage' || process.env.NODE_ENV === 'coverage') {
karmaConfig.reporters.push('coverage-istanbul');
karmaConfig.coverageIstanbulReporter = {
reports: ['html', 'text-summary', 'cobertura'],
dir: 'coverage-javascript/',
subdir: '.',
fixWebpackSourcePaths: true,
};
karmaConfig.browserNoActivityTimeout = 60000; // 60 seconds
}
if (process.env.DEBUG) {
karmaConfig.logLevel = config.LOG_DEBUG;
process.env.CHROME_LOG_FILE = process.env.CHROME_LOG_FILE || 'chrome_debug.log';
}
if (process.env.CHROME_LOG_FILE) {
karmaConfig.customLaunchers.ChromeHeadlessCustom.flags.push('--enable-logging', '--v=1');
}
config.set(karmaConfig);
};

View file

@ -154,7 +154,6 @@ const alias = {
vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'),
vue$: 'vue/dist/vue.esm.js',
jquery$: 'jquery/dist/jquery.slim.js',
spec: path.join(ROOT_PATH, 'spec/javascripts'),
jest: path.join(ROOT_PATH, 'spec/frontend'),
shared_queries: path.join(ROOT_PATH, 'app/graphql/queries'),
@ -178,7 +177,6 @@ if (IS_EE) {
ee_empty_states: path.join(ROOT_PATH, 'ee/app/views/shared/empty_states'),
ee_icons: path.join(ROOT_PATH, 'ee/app/views/shared/icons'),
ee_images: path.join(ROOT_PATH, 'ee/app/assets/images'),
ee_spec: path.join(ROOT_PATH, 'ee/spec/javascripts'),
ee_jest: path.join(ROOT_PATH, 'ee/spec/frontend'),
ee_else_ce: path.join(ROOT_PATH, 'ee/app/assets/javascripts'),
});
@ -191,7 +189,6 @@ if (IS_JH) {
jh_empty_states: path.join(ROOT_PATH, 'jh/app/views/shared/empty_states'),
jh_icons: path.join(ROOT_PATH, 'jh/app/views/shared/icons'),
jh_images: path.join(ROOT_PATH, 'jh/app/assets/images'),
jh_spec: path.join(ROOT_PATH, 'jh/spec/javascripts'),
jh_jest: path.join(ROOT_PATH, 'jh/spec/frontend'),
jh_else_ce: path.join(ROOT_PATH, 'jh/app/assets/javascripts'),
});

View file

@ -1,51 +0,0 @@
# frozen_string_literal: true
# rubocop:disable Style/SignalException
def get_karma_files(files)
files.select do |file|
file.start_with?('ee/spec/javascripts', 'spec/javascripts') &&
file.end_with?('_spec.js') &&
!file.end_with?('browser_spec.js')
end
end
new_karma_files = get_karma_files(git.added_files.to_a)
unless new_karma_files.empty?
if helper.ci?
markdown(<<~MARKDOWN)
## New karma spec file
New frontend specs ([except `browser_specs`](https://gitlab.com/gitlab-org/gitlab/blob/3b6fe2f1077eedb0b8aff02a7350234f0b7dc4f9/spec/javascripts/lib/utils/browser_spec.js#L2)) should be
[written in jest](https://docs.gitlab.com/ee/development/testing_guide/frontend_testing.html#jest).
You have created the following tests, please migrate them over to jest:
* #{new_karma_files.map { |path| "`#{path}`" }.join("\n* ")}
MARKDOWN
end
fail "You have created a new karma spec file"
end
changed_karma_files = get_karma_files(helper.all_changed_files) - new_karma_files
return if changed_karma_files.empty?
warn 'You have edited karma spec files. Please consider migrating them to jest.'
if helper.ci?
markdown(<<~MARKDOWN)
## Edited karma files
You have edited the following karma spec files. Please consider migrating them to jest:
* #{changed_karma_files.map { |path| "`#{path}`" }.join("\n* ")}
In order to align with our Iteration value, migration can also be done as a follow-up.
For more information: [Jestodus epic](https://gitlab.com/groups/gitlab-org/-/epics/895)
MARKDOWN
end

View file

@ -155,10 +155,8 @@ graph RL;
3_1-1["jest (16 minutes)"];
class 3_1-1 criticalPath;
click 3_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914204&udv=0"
3_1-2["karma (2 minutes)"];
click 3_1-3 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914200&udv=0"
subgraph "Needs `rspec frontend_fixture/rspec-ee frontend_fixture`";
3_1-1 & 3_1-2 --> 2_2-2;
3_1-1 --> 2_2-2;
end
3_2-1["rspec:coverage (5.3 minutes)"];
@ -268,10 +266,8 @@ graph RL;
3_1-1["jest (16 minutes)"];
class 3_1-1 criticalPath;
click 3_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914204&udv=0"
3_1-2["karma (2 minutes)"];
click 3_1-3 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914200&udv=0"
subgraph "Needs `rspec frontend_fixture/rspec-ee frontend_fixture`";
3_1-1 & 3_1-2 --> 2_2-2;
3_1-1 --> 2_2-2;
end
3_2-1["rspec:coverage (5.3 minutes)"];
@ -627,7 +623,6 @@ that is deployed in stage `review`.
the `qa` stage's jobs (for example, Review App performance report).
- `pages`: This stage includes a job that deploys the various reports as
GitLab Pages (for example, [`coverage-ruby`](https://gitlab-org.gitlab.io/gitlab/coverage-ruby/),
[`coverage-javascript`](https://gitlab-org.gitlab.io/gitlab/coverage-javascript/),
and `webpack-report` (found at `https://gitlab-org.gitlab.io/gitlab/webpack-report/`, but there is
[an issue with the deployment](https://gitlab.com/gitlab-org/gitlab/-/issues/233458)).
- `notify`: This stage includes jobs that notify various failures to Slack.

View file

@ -128,7 +128,6 @@ In order to run the test you can use the following commands:
- `bin/rake spec:unit` to run only the unit tests
- `bin/rake spec:integration` to run only the integration tests
- `bin/rake spec:system` to run only the system tests
- `bin/rake karma` to run the Karma test suite
`bin/rake spec` takes significant time to pass.
Instead of running the full test suite locally, you can save a lot of time by running

View file

@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Frontend testing standards and style guidelines
There are two types of test suites encountered while developing frontend code
at GitLab. We use Karma with Jasmine and Jest for JavaScript unit and integration testing,
at GitLab. We use Jest for JavaScript unit and integration testing,
and RSpec feature tests with Capybara for e2e (end-to-end) integration testing.
Unit and feature tests need to be written for all new features.
@ -28,46 +28,9 @@ If you are looking for a guide on Vue component testing, you can jump right away
We use Jest to write frontend unit and integration tests.
Jest tests can be found in `/spec/frontend` and `/ee/spec/frontend` in EE.
## Karma test suite
While GitLab has switched over to [Jest](https://jestjs.io), Karma tests still exist in our
application because some of our specs require a browser and can't be easily migrated to Jest.
Those specs intend to eventually drop Karma in favor of either Jest or RSpec. You can track this migration
in the [related epic](https://gitlab.com/groups/gitlab-org/-/epics/4900).
[Karma](http://karma-runner.github.io/) is a test runner which uses
[Jasmine](https://jasmine.github.io/) as its test framework. Jest also uses Jasmine as foundation,
that's why it's looking quite similar.
Karma tests live in `spec/javascripts/` and `/ee/spec/javascripts` in EE.
`app/assets/javascripts/behaviors/autosize.js`
might have a corresponding `spec/javascripts/behaviors/autosize_spec.js` file.
Keep in mind that in a CI environment, these tests are run in a headless
browser and you don't have access to certain APIs, such as
[`Notification`](https://developer.mozilla.org/en-US/docs/Web/API/notification),
which have to be stubbed.
### Differences to Karma
- Jest runs in a Node.js environment, not in a browser. [An issue exists](https://gitlab.com/gitlab-org/gitlab/-/issues/26982) for running Jest tests in a browser.
- Because Jest runs in a Node.js environment, it uses [jsdom](https://github.com/jsdom/jsdom) by default. See also its [limitations](#limitations-of-jsdom) below.
- Jest does not have access to Webpack loaders or aliases.
The aliases used by Jest are defined in its [own configuration](https://gitlab.com/gitlab-org/gitlab/-/blob/master/jest.config.js).
- All calls to `setTimeout` and `setInterval` are mocked away. See also [Jest Timer Mocks](https://jestjs.io/docs/timer-mocks).
- `rewire` is not required because Jest supports mocking modules. See also [Manual Mocks](https://jestjs.io/docs/manual-mocks).
- No [context object](https://jasmine.github.io/tutorials/your_first_suite#section-The_%3Ccode%3Ethis%3C/code%3E_keyword) is passed to tests in Jest.
This means sharing `this.something` between `beforeEach()` and `it()` for example does not work.
Instead you should declare shared variables in the context that they are needed (via `const` / `let`).
- The following cause tests to fail in Jest:
- Unmocked requests.
- Unhandled Promise rejections.
- Calls to `console.warn`, including warnings from libraries like Vue.
### Limitations of jsdom
As mentioned [above](#differences-to-karma), Jest uses jsdom instead of a browser for running tests.
Jest uses jsdom instead of a browser for running tests.
This comes with a number of limitations, namely:
- [No scrolling support](https://github.com/jsdom/jsdom/blob/15.1.1/lib/jsdom/browser/Window.js#L623-L625)
@ -387,8 +350,7 @@ Sometimes we have to test time-sensitive code. For example, recurring events tha
If the application itself is waiting for some time, mock await the waiting. In Jest this is already
[done by default](https://gitlab.com/gitlab-org/gitlab/-/blob/a2128edfee799e49a8732bfa235e2c5e14949c68/jest.config.js#L47)
(see also [Jest Timer Mocks](https://jestjs.io/docs/timer-mocks)). In Karma you can use the
[Jasmine mock clock](https://jasmine.github.io/api/2.9/Clock.html).
(see also [Jest Timer Mocks](https://jestjs.io/docs/timer-mocks)).
```javascript
const doSomethingLater = () => {
@ -409,20 +371,6 @@ it('does something', () => {
});
```
**in Karma:**
```javascript
it('does something', () => {
jasmine.clock().install();
doSomethingLater();
jasmine.clock().tick(4000);
expect(something).toBe('done');
jasmine.clock().uninstall();
});
```
### Mocking the current location in Jest
NOTE:
@ -476,8 +424,7 @@ it('passes', () => {
Sometimes a test needs to wait for something to happen in the application before it continues.
Avoid using [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout)
because it makes the reason for waiting unclear and if used within Karma with a time larger than zero it slows down our test suite.
Instead use one of the following approaches.
because it makes the reason for waiting unclear. Instead use one of the following approaches.
#### Promises and Ajax calls
@ -505,19 +452,6 @@ it('waits for an Ajax call', async () => {
});
```
**in Karma:**
```javascript
it('waits for an Ajax call', done => {
askTheServer()
.then(() => {
expect(something).toBe('done');
})
.then(done)
.catch(done.fail);
});
```
If you are not able to register handlers to the `Promise`, for example because it is executed in a synchronous Vue life cycle hook, take a look at the [waitFor](#wait-until-axios-requests-finish) helpers or you can flush all pending `Promise`s:
**in Jest:**
@ -548,22 +482,6 @@ it('renders something', () => {
});
```
**in Karma:**
```javascript
it('renders something', done => {
wrapper.setProps({ value: 'new value' });
wrapper.vm
.$nextTick()
.then(() => {
expect(wrapper.text()).toBe('new value');
})
.then(done)
.catch(done.fail);
});
```
#### Events
If the application triggers an event that you need to wait for in your test, register an event handler which contains
@ -776,7 +694,7 @@ TBU
### Stubbing and Mocking
Jasmine provides stubbing and mocking capabilities. There are some subtle differences in how to use it within Karma and Jest.
Jasmine provides stubbing and mocking capabilities. There are some subtle differences in how to use it within Jest.
Stubs or spies are often used synonymously. In Jest it's quite easy thanks to the `.spyOn` method.
[Official docs](https://jestjs.io/docs/jest-object#jestspyonobject-methodname)
@ -835,7 +753,6 @@ For running the frontend tests, you need the following commands:
- `rake frontend:fixtures` (re-)generates [fixtures](#frontend-test-fixtures). Make sure that
fixtures are up-to-date before running tests that require them.
- `yarn jest` runs Jest tests.
- `yarn karma` runs Karma tests.
### Live testing and focused testing -- Jest
@ -860,50 +777,37 @@ yarn jest ./path/to/folder/
yarn jest term
```
### Live testing and focused testing -- Karma
Karma allows something similar, but it's way more costly.
Running Karma with `yarn run karma-start` compiles the JavaScript
assets and runs a server at `http://localhost:9876/` where it automatically
runs the tests on any browser which connects to it. You can enter that URL on
multiple browsers at once to have it run the tests on each in parallel.
While Karma is running, any changes you make instantly trigger a recompile
and retest of the **entire test suite**, so you can see instantly if you've broken
a test with your changes. You can use [Jasmine focused](https://jasmine.github.io/2.5/focused_specs.html) or
excluded tests (with `fdescribe` or `xdescribe`) to get Karma to run only the
tests you want while you're working on a specific feature, but make sure to
remove these directives when you commit your code.
It is also possible to only run Karma on specific folders or files by filtering
the run tests via the argument `--filter-spec` or short `-f`:
```shell
# Run all files
yarn karma-start
# Run specific spec files
yarn karma-start --filter-spec profile/account/components/update_username_spec.js
# Run specific spec folder
yarn karma-start --filter-spec profile/account/components/
# Run all specs which path contain vue_shared or vie
yarn karma-start -f vue_shared -f vue_mr_widget
```
You can also use glob syntax to match files. Remember to put quotes around the
glob otherwise your shell may split it into multiple arguments:
```shell
# Run all specs named `file_spec` within the IDE subdirectory
yarn karma -f 'spec/javascripts/ide/**/file_spec.js'
```
## Frontend test fixtures
Frontend fixtures are files containing responses from backend controllers. These responses can be either HTML
generated from HAML templates or JSON payloads. Frontend tests that rely on these responses are
often using fixtures to validate correct integration with the backend code.
### Use fixtures
Jest uses `spec/frontend/__helpers__/fixtures.js` to import fixtures in tests.
The following are examples of tests that work for Jest:
```javascript
it('makes a request', () => {
const responseBody = getJSONFixture('some/fixture.json'); // loads spec/frontend/fixtures/some/fixture.json
axiosMock.onGet(endpoint).reply(200, responseBody);
myButton.click();
// ...
});
it('uses some HTML element', () => {
loadFixtures('some/page.html'); // loads spec/frontend/fixtures/some/page.html and adds it to the DOM
const element = document.getElementById('#my-id');
// ...
});
```
### Generate fixtures
You can find code to generate test fixtures in:
@ -961,34 +865,6 @@ This will create a new fixture located at
You can import the JSON fixture in a Jest test using the `getJSONFixture` method
[as described below](#use-fixtures).
### Use fixtures
Jest and Karma test suites import fixtures in different ways:
- The Karma test suite are served by [jasmine-jquery](https://github.com/velesin/jasmine-jquery).
- Jest use `spec/frontend/__helpers__/fixtures.js`.
The following are examples of tests that work for both Karma and Jest:
```javascript
it('makes a request', () => {
const responseBody = getJSONFixture('some/fixture.json'); // loads spec/frontend/fixtures/some/fixture.json
axiosMock.onGet(endpoint).reply(200, responseBody);
myButton.click();
// ...
});
it('uses some HTML element', () => {
loadFixtures('some/page.html'); // loads spec/frontend/fixtures/some/page.html and adds it to the DOM
const element = document.getElementById('#my-id');
// ...
});
```
## Data-driven tests
Similar to [RSpec's parameterized tests](best_practices.md#table-based--parameterized-tests),
@ -1139,13 +1015,10 @@ Main information on frontend testing levels can be found in the [Testing Levels
Tests relevant for frontend development can be found at the following places:
- `spec/javascripts/`, for Karma tests
- `spec/frontend/`, for Jest tests
- `spec/features/`, for RSpec tests
RSpec runs complete [feature tests](testing_levels.md#frontend-feature-tests), while the Jest and Karma directories contain [frontend unit tests](testing_levels.md#frontend-unit-tests), [frontend component tests](testing_levels.md#frontend-component-tests), and [frontend integration tests](testing_levels.md#frontend-integration-tests).
All tests in `spec/javascripts/` are intended to be migrated to `spec/frontend/` (see also [#52483](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/52483)).
RSpec runs complete [feature tests](testing_levels.md#frontend-feature-tests), while the Jest directories contain [frontend unit tests](testing_levels.md#frontend-unit-tests), [frontend component tests](testing_levels.md#frontend-component-tests), and [frontend integration tests](testing_levels.md#frontend-integration-tests).
Before May 2018, `features/` also contained feature tests run by Spinach. These tests were removed from the codebase in May 2018 ([#23036](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/23036)).

View file

@ -19,7 +19,7 @@ importance.
GitLab is built on top of [Ruby on Rails](https://rubyonrails.org/), and we're using [RSpec](https://github.com/rspec/rspec-rails#feature-specs) for all
the backend tests, with [Capybara](https://github.com/teamcapybara/capybara) for end-to-end integration testing.
On the frontend side, we're using [Jest](https://jestjs.io/) and [Karma](http://karma-runner.github.io/)/[Jasmine](https://jasmine.github.io/) for JavaScript unit and
On the frontend side, we're using [Jest](https://jestjs.io/) for JavaScript unit and
integration testing.
Following are two great articles that everyone should read to understand what
@ -40,7 +40,7 @@ system tests, parameterized tests etc.
## [Frontend testing standards and style guidelines](frontend_testing.md)
Everything you should know about how to write good Frontend tests: Karma,
Everything you should know about how to write good Frontend tests: Jest,
testing promises, stubbing etc.
## [Flaky tests](flaky_tests.md)

View file

@ -31,7 +31,7 @@ records should use stubs/doubles as much as possible.
| Code path | Tests path | Testing engine | Notes |
| --------- | ---------- | -------------- | ----- |
| `app/assets/javascripts/` | `spec/javascripts/`, `spec/frontend/` | Karma & Jest | More details in the [Frontend Testing guide](frontend_testing.md) section. |
| `app/assets/javascripts/` | `spec/frontend/` | Jest | More details in the [Frontend Testing guide](frontend_testing.md) section. |
| `app/finders/` | `spec/finders/` | RSpec | |
| `app/graphql/` | `spec/graphql/` | RSpec | |
| `app/helpers/` | `spec/helpers/` | RSpec | |
@ -233,7 +233,7 @@ They're useful to test permissions, redirections, what view is rendered etc.
| `app/controllers/` | `spec/requests/`, `spec/controllers` | RSpec | Request specs are preferred over legacy controller specs. |
| `app/mailers/` | `spec/mailers/` | RSpec | |
| `lib/api/` | `spec/requests/api/` | RSpec | |
| `app/assets/javascripts/` | `spec/javascripts/`, `spec/frontend/` | Karma & Jest | [More details below](#frontend-integration-tests) |
| `app/assets/javascripts/` | `spec/frontend/` | Jest | [More details below](#frontend-integration-tests) |
### Frontend integration tests
@ -322,13 +322,6 @@ controller.instance_variable_set(:@user, user)
and use methods [deprecated in Rails 5](https://gitlab.com/gitlab-org/gitlab/-/issues/16260).
### About Karma
Karma is both in the Unit tests and the Integration tests category. Karma provides an environment to
run JavaScript tests, so you can either run unit tests (e.g. test a single
JavaScript method), or integration tests (e.g. test a component that is composed
of multiple components).
## White-box tests at the system level (formerly known as System / Feature tests)
Formal definitions:

View file

@ -3,15 +3,3 @@ const baseConfig = require('./jest.config.base');
module.exports = {
...baseConfig('spec/frontend'),
};
const karmaTestFile = process.argv.find((arg) => arg.includes('spec/javascripts/'));
if (karmaTestFile) {
console.error(`
Files in spec/javascripts/ and ee/spec/javascripts need to be run with Karma.
Please use the following command instead:
yarn karma -f ${karmaTestFile}
`);
process.exit(1);
}

View file

@ -1,19 +0,0 @@
# frozen_string_literal: true
unless Rails.env.production?
namespace :karma do
# alias exists for legacy reasons
desc 'GitLab | Karma | Generate fixtures for JavaScript tests'
task fixtures: ['frontend:fixtures']
desc 'GitLab | Karma | Run JavaScript tests'
task tests: ['yarn:check'] do
sh "yarn run karma" do |ok, res|
abort('rake karma:tests failed') unless ok
end
end
end
desc 'GitLab | Karma | Shortcut for karma:fixtures and karma:tests'
task karma: ['karma:fixtures', 'karma:tests']
end

View file

@ -15,10 +15,6 @@
"jest-debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
"jest:integration": "jest --config jest.config.integration.js",
"jsdoc": "jsdoc -c config/jsdocs.config.js",
"prekarma": "yarn check-dependencies",
"karma": "BABEL_ENV=${BABEL_ENV:=karma} karma start --single-run true config/karma.config.js",
"karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "BABEL_ENV=karma karma start config/karma.config.js",
"lint:eslint": "yarn run internal:eslint",
"lint:eslint:fix": "yarn run internal:eslint --fix",
"lint:eslint:all": "yarn run internal:eslint .",
@ -242,14 +238,6 @@
"jest-util": "^26.5.2",
"jsdoc": "^3.5.5",
"jsdoc-vue": "^1.0.0",
"karma": "^4.2.0",
"karma-chrome-launcher": "^3.0.0",
"karma-coverage-istanbul-reporter": "^2.1.0",
"karma-jasmine": "^1.1.2",
"karma-junit-reporter": "^1.2.0",
"karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.2",
"markdownlint-cli": "0.26.0",
"md5": "^2.2.1",
"miragejs": "^0.1.40",

View file

@ -71,8 +71,7 @@ module QA
# adequately tested elsewhere.
#
# There are also FE specs
# * ee/spec/javascripts/ide/components/terminal/terminal_spec.js
# * ee/spec/frontend/ide/components/terminal/terminal_controls_spec.js
# * spec/frontend/ide/components/terminal/terminal_controls_spec.js
Page::Project::WebIDE::Edit.perform do |edit|
edit.start_web_terminal

View file

@ -1,44 +0,0 @@
#!/usr/bin/env bash
karma_directory=spec/javascripts
if [ -d ee ]; then
karma_directory="$karma_directory ee/$karma_directory"
fi
karma_files=$(find $karma_directory -type f -name '*_spec.js' -not -path '*/helpers/*')
violations=""
for karma_file in $karma_files; do
jest_file=${karma_file/spec\/javascripts/"spec/frontend"}
if [ -f $jest_file ]; then
violations="$violations $jest_file"
fi
done
if [[ -z "$violations" ]]; then
echo "All good!"
exit 0
else
echo "Danger! The following Jest specs have corresponding files in the Karma spec directory (i.e. spec/javascripts):"
echo ""
echo "------------------------------"
for file in $violations; do
echo $file
done
echo "------------------------------"
echo ""
echo "For each of these files, please either:"
echo ""
echo "1. Fully migrate the file to Jest and remove the corresponding Karma file."
echo "2. Remove the Jest file for now, make any relevant changes in the corresponding Karma file, and handle the migration to Jest in a separate MR."
echo ""
echo "Why is this a problem?"
echo ""
echo "- It's nice to have a single source of truth for the unit tests of a subject."
echo "- This will cause conflicts if the remaining Karma spec is migrated using our automated tool."
echo " https://gitlab.com/gitlab-org/frontend/playground/migrate-karma-to-jest"
echo ""
exit 1
fi

View file

@ -14,7 +14,7 @@ const fs = require('fs');
const path = require('path');
const sourceDirectories = ['app/assets/javascripts'];
const testDirectories = ['spec/javascripts', 'spec/frontend'];
const testDirectories = ['spec/frontend'];
if (fs.existsSync('ee')) {
sourceDirectories.forEach((dir) => {

View file

@ -51,8 +51,7 @@ class StaticAnalysis
Task.new(%w[scripts/lint-conflicts.sh], 1),
Task.new(%w[yarn run block-dependencies], 1),
Task.new(%w[scripts/lint-rugged], 1),
Task.new(%w[scripts/gemfile_lock_changed.sh], 1),
Task.new(%w[scripts/frontend/check_no_partial_karma_jest.sh], 1)
Task.new(%w[scripts/gemfile_lock_changed.sh], 1)
].compact.freeze
def run_tasks!(options = {})

View file

@ -1,39 +0,0 @@
---
env:
jasmine: true
extends: plugin:jasmine/recommended
globals:
appendLoadFixtures: false
appendLoadStyleFixtures: false
appendSetFixtures: false
appendSetStyleFixtures: false
getJSONFixture: false
loadFixtures: false
loadJSONFixtures: false
loadStyleFixtures: false
preloadFixtures: false
preloadStyleFixtures: false
readFixtures: false
sandbox: false
setFixtures: false
setStyleFixtures: false
spyOnDependency: false
spyOnEvent: false
ClassSpecHelper: false
plugins:
- jasmine
rules:
func-names: off
jasmine/no-suite-dupes:
- warn
- branch
jasmine/no-spec-dupes:
- warn
- branch
prefer-arrow-callback: off
import/no-unresolved:
- error
- ignore:
- 'fixtures/blob'
# Temporarily disabled to facilitate an upgrade to eslint-plugin-jasmine
jasmine/prefer-toHaveBeenCalledWith: off

View file

@ -1,2 +0,0 @@
*
!.gitignore

View file

@ -1,334 +0,0 @@
// this file can't be migrated to jest because it relies on the browser to perform integration tests:
// (specifically getClientBoundingRect and mouse movements)
// see: https://gitlab.com/groups/gitlab-org/-/epics/895#what-if-theres-a-karma-spec-which-is-simply-unmovable-to-jest-ie-it-is-dependent-on-a-running-browser-environment
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { SIDEBAR_COLLAPSED_CLASS } from '~/contextual_sidebar';
import {
calculateTop,
showSubLevelItems,
canShowSubItems,
canShowActiveSubItems,
mouseEnterTopItems,
mouseLeaveTopItem,
getOpenMenu,
setOpenMenu,
mousePos,
getHideSubItemsInterval,
documentMouseMove,
getHeaderHeight,
setSidebar,
subItemsMouseLeave,
} from '~/fly_out_nav';
describe('Fly out sidebar navigation', () => {
let el;
let breakpointSize = 'lg';
beforeEach(() => {
el = document.createElement('div');
el.style.position = 'relative';
document.body.appendChild(el);
spyOn(GlBreakpointInstance, 'getBreakpointSize').and.callFake(() => breakpointSize);
setOpenMenu(null);
});
afterEach(() => {
document.body.innerHTML = '';
breakpointSize = 'lg';
mousePos.length = 0;
setSidebar(null);
});
describe('calculateTop', () => {
it('returns boundingRect top', () => {
const boundingRect = {
top: 100,
height: 100,
};
expect(calculateTop(boundingRect, 100)).toBe(100);
});
it('returns boundingRect - bottomOverflow', () => {
const boundingRect = {
top: window.innerHeight - 50,
height: 100,
};
expect(calculateTop(boundingRect, 100)).toBe(window.innerHeight - 50);
});
});
describe('getHideSubItemsInterval', () => {
beforeEach(() => {
el.innerHTML =
'<div class="sidebar-sub-level-items" style="position: fixed; top: 0; left: 100px; height: 150px;"></div>';
});
it('returns 0 if currentOpenMenu is nil', () => {
expect(getHideSubItemsInterval()).toBe(0);
});
it('returns 0 if mousePos is empty', () => {
expect(getHideSubItemsInterval()).toBe(0);
});
it('returns 0 when mouse above sub-items', () => {
showSubLevelItems(el);
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top,
});
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top - 50,
});
expect(getHideSubItemsInterval()).toBe(0);
});
it('returns 0 when mouse is below sub-items', () => {
const subItems = el.querySelector('.sidebar-sub-level-items');
showSubLevelItems(el);
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top,
});
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top - subItems.getBoundingClientRect().height + 50,
});
expect(getHideSubItemsInterval()).toBe(0);
});
it('returns 300 when mouse is moved towards sub-items', () => {
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top,
});
showSubLevelItems(el);
documentMouseMove({
clientX: el.getBoundingClientRect().left + 20,
clientY: el.getBoundingClientRect().top + 10,
});
expect(getHideSubItemsInterval()).toBe(300);
});
});
describe('mouseLeaveTopItem', () => {
beforeEach(() => {
spyOn(el.classList, 'remove');
});
it('removes is-over class if currentOpenMenu is null', () => {
mouseLeaveTopItem(el);
expect(el.classList.remove).toHaveBeenCalledWith('is-over');
});
it('removes is-over class if currentOpenMenu is null & there are sub-items', () => {
el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
mouseLeaveTopItem(el);
expect(el.classList.remove).toHaveBeenCalledWith('is-over');
});
it('does not remove is-over class if currentOpenMenu is the passed in sub-items', () => {
el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
mouseLeaveTopItem(el);
expect(el.classList.remove).not.toHaveBeenCalled();
});
});
describe('mouseEnterTopItems', () => {
beforeEach(() => {
el.innerHTML =
'<div class="sidebar-sub-level-items" style="position: absolute; top: 0; left: 100px; height: 200px;"></div>';
});
it('shows sub-items after 0ms if no menu is open', (done) => {
mouseEnterTopItems(el);
expect(getHideSubItemsInterval()).toBe(0);
setTimeout(() => {
expect(el.querySelector('.sidebar-sub-level-items').style.display).toBe('block');
done();
});
});
it('shows sub-items after 300ms if a menu is currently open', (done) => {
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top,
});
setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
documentMouseMove({
clientX: el.getBoundingClientRect().left + 20,
clientY: el.getBoundingClientRect().top + 10,
});
mouseEnterTopItems(el, 0);
expect(getHideSubItemsInterval()).toBe(300);
setTimeout(() => {
expect(el.querySelector('.sidebar-sub-level-items').style.display).toBe('block');
done();
});
});
});
describe('showSubLevelItems', () => {
beforeEach(() => {
el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
});
it('adds is-over class to el', () => {
spyOn(el.classList, 'add');
showSubLevelItems(el);
expect(el.classList.add).toHaveBeenCalledWith('is-over');
});
it('does not show sub-items on mobile', () => {
breakpointSize = 'xs';
showSubLevelItems(el);
expect(el.querySelector('.sidebar-sub-level-items').style.display).not.toBe('block');
});
it('shows sub-items', () => {
showSubLevelItems(el);
expect(el.querySelector('.sidebar-sub-level-items').style.display).toBe('block');
});
it('shows collapsed only sub-items if icon only sidebar', () => {
const subItems = el.querySelector('.sidebar-sub-level-items');
const sidebar = document.createElement('div');
sidebar.classList.add(SIDEBAR_COLLAPSED_CLASS);
subItems.classList.add('is-fly-out-only');
setSidebar(sidebar);
showSubLevelItems(el);
expect(el.querySelector('.sidebar-sub-level-items').style.display).toBe('block');
});
it('does not show collapsed only sub-items if icon only sidebar', () => {
const subItems = el.querySelector('.sidebar-sub-level-items');
subItems.classList.add('is-fly-out-only');
showSubLevelItems(el);
expect(subItems.style.display).not.toBe('block');
});
it('sets transform of sub-items', () => {
const sidebar = document.createElement('div');
const subItems = el.querySelector('.sidebar-sub-level-items');
sidebar.style.width = '200px';
document.body.appendChild(sidebar);
setSidebar(sidebar);
showSubLevelItems(el);
expect(subItems.style.transform).toBe(
`translate3d(200px, ${
Math.floor(el.getBoundingClientRect().top) - getHeaderHeight()
}px, 0px)`,
);
});
it('sets is-above when element is above', () => {
const subItems = el.querySelector('.sidebar-sub-level-items');
subItems.style.height = `${window.innerHeight + el.offsetHeight}px`;
el.style.top = `${window.innerHeight - el.offsetHeight}px`;
spyOn(subItems.classList, 'add');
showSubLevelItems(el);
expect(subItems.classList.add).toHaveBeenCalledWith('is-above');
});
});
describe('canShowSubItems', () => {
it('returns true if on desktop size', () => {
expect(canShowSubItems()).toBeTruthy();
});
it('returns false if on mobile size', () => {
breakpointSize = 'xs';
expect(canShowSubItems()).toBeFalsy();
});
});
describe('canShowActiveSubItems', () => {
it('returns true by default', () => {
expect(canShowActiveSubItems(el)).toBeTruthy();
});
it('returns false when active & expanded sidebar', () => {
const sidebar = document.createElement('div');
el.classList.add('active');
setSidebar(sidebar);
expect(canShowActiveSubItems(el)).toBeFalsy();
});
it('returns true when active & collapsed sidebar', () => {
const sidebar = document.createElement('div');
sidebar.classList.add(SIDEBAR_COLLAPSED_CLASS);
el.classList.add('active');
setSidebar(sidebar);
expect(canShowActiveSubItems(el)).toBeTruthy();
});
});
describe('subItemsMouseLeave', () => {
beforeEach(() => {
el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute;"></div>';
setOpenMenu(el.querySelector('.sidebar-sub-level-items'));
});
it('hides subMenu if element is not hovered', () => {
subItemsMouseLeave(el);
expect(getOpenMenu()).toBeNull();
});
it('does not hide subMenu if element is hovered', () => {
el.classList.add('is-over');
subItemsMouseLeave(el);
expect(getOpenMenu()).not.toBeNull();
});
});
});

View file

@ -1 +0,0 @@
export * from '../../../frontend/lib/utils/mock_data';

View file

@ -1,145 +0,0 @@
/* eslint-disable
jasmine/no-global-setup, no-underscore-dangle, no-console
*/
import { config as testUtilsConfig } from '@vue/test-utils';
import jasmineDiff from 'jasmine-diff';
import $ from 'jquery';
import 'core-js/features/set-immediate';
import 'vendor/jasmine-jquery';
import '~/commons';
import Vue from 'vue';
import { getDefaultAdapter } from '~/lib/utils/axios_utils';
import Translate from '~/vue_shared/translate';
import { FIXTURES_PATH, TEST_HOST } from './test_constants';
// Tech debt issue TBD
testUtilsConfig.logModifiedComponents = false;
const isHeadlessChrome = /\bHeadlessChrome\//.test(navigator.userAgent);
Vue.config.devtools = !isHeadlessChrome;
Vue.config.productionTip = false;
let hasVueWarnings = false;
Vue.config.warnHandler = (msg, vm, trace) => {
// The following workaround is necessary, so we are able to use setProps from Vue test utils
// see https://github.com/vuejs/vue-test-utils/issues/631#issuecomment-421108344
const currentStack = new Error().stack;
const isInVueTestUtils = currentStack
.split('\n')
.some((line) => line.startsWith(' at VueWrapper.setProps ('));
if (isInVueTestUtils) {
return;
}
hasVueWarnings = true;
fail(`${msg}${trace}`);
};
let hasVueErrors = false;
Vue.config.errorHandler = function (err) {
hasVueErrors = true;
fail(err);
};
Vue.use(Translate);
// enable test fixtures
jasmine.getFixtures().fixturesPath = FIXTURES_PATH;
jasmine.getJSONFixtures().fixturesPath = FIXTURES_PATH;
beforeAll(() => {
jasmine.addMatchers(
jasmineDiff(jasmine, {
colors: window.__karma__.config.color,
inline: window.__karma__.config.color,
}),
);
});
// globalize common libraries
window.$ = $;
window.jQuery = window.$;
// stub expected globals
window.gl = window.gl || {};
window.gl.TEST_HOST = TEST_HOST;
window.gon = window.gon || {};
window.gon.test_env = true;
window.gon.ee = process.env.IS_EE;
gon.relative_url_root = '';
let hasUnhandledPromiseRejections = false;
window.addEventListener('unhandledrejection', (event) => {
hasUnhandledPromiseRejections = true;
console.error('Unhandled promise rejection:');
console.error(event.reason.stack || event.reason);
});
let longRunningTestTimeoutHandle;
beforeEach((done) => {
longRunningTestTimeoutHandle = setTimeout(() => {
done.fail('Test is running too long!');
}, 4000);
done();
});
afterEach(() => {
clearTimeout(longRunningTestTimeoutHandle);
});
const axiosDefaultAdapter = getDefaultAdapter();
// render all of our tests
const testContexts = [require.context('spec', true, /_spec$/)];
if (process.env.IS_EE) {
testContexts.push(require.context('ee_spec', true, /_spec$/));
}
testContexts.forEach((context) => {
context.keys().forEach((path) => {
try {
context(path);
} catch (err) {
console.log(err);
console.error('[GL SPEC RUNNER ERROR] Unable to load spec: ', path);
describe('Test bundle', function () {
it(`includes '${path}'`, function () {
expect(err).toBeNull();
});
});
}
});
});
describe('test errors', () => {
beforeAll((done) => {
if (hasUnhandledPromiseRejections || hasVueWarnings || hasVueErrors) {
setTimeout(done, 1000);
} else {
done();
}
});
it('has no unhandled Promise rejections', () => {
expect(hasUnhandledPromiseRejections).toBe(false);
});
it('has no Vue warnings', () => {
expect(hasVueWarnings).toBe(false);
});
it('has no Vue error', () => {
expect(hasVueErrors).toBe(false);
});
it('restores axios adapter after mocking', () => {
if (getDefaultAdapter() !== axiosDefaultAdapter) {
fail('axios adapter is not restored! Did you forget a restore() on MockAdapter?');
}
});
});

View file

@ -1 +0,0 @@
export * from '../frontend/__helpers__/test_constants';

View file

@ -60,7 +60,6 @@
'app/views/foo' | [:frontend]
'public/foo' | [:frontend]
'scripts/frontend/foo' | [:frontend]
'spec/javascripts/foo' | [:frontend]
'spec/frontend/bar' | [:frontend]
'spec/frontend_integration/bar' | [:frontend]
'vendor/assets/foo' | [:frontend]
@ -73,7 +72,6 @@
'ee/app/assets/foo' | [:frontend]
'ee/app/views/foo' | [:frontend]
'ee/spec/javascripts/foo' | [:frontend]
'ee/spec/frontend/bar' | [:frontend]
'ee/spec/frontend_integration/bar' | [:frontend]
@ -220,7 +218,7 @@
describe '.local_warning_message' do
it 'returns an informational message with rules that can run' do
expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changelog, database, documentation, duplicate_yarn_dependencies, eslint, gitaly, karma, pajamas, pipeline, prettier, product_intelligence, utility_css, vue_shared_documentation')
expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changelog, database, documentation, duplicate_yarn_dependencies, eslint, gitaly, pajamas, pipeline, prettier, product_intelligence, utility_css, vue_shared_documentation')
end
end

View file

@ -10,7 +10,6 @@ module ProjectHelper
duplicate_yarn_dependencies
eslint
gitaly
karma
pajamas
pipeline
prettier

View file

@ -1,854 +0,0 @@
/* eslint-disable */
/*
Jasmine JQuery 3.0 patched version from this fork : https://github.com/cmrd-senya/jasmine-jquery/blob/jquery3/lib/jasmine-jquery.js
*/
/*!
Jasmine-jQuery: a set of jQuery helpers for Jasmine tests.
Version 2.1.1
https://github.com/velesin/jasmine-jquery
Copyright (c) 2010-2014 Wojciech Zawistowski, Travis Jeffery
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
(function (root, factory) {
if (typeof module !== 'undefined' && module.exports) {
// The line below is patched from jquery => jquery/dist/jquery
// in order to load a jQuery with ajax, so that this testing library
// doesn't break
factory(root, root.jasmine, require('jquery/dist/jquery'));
} else {
factory(root, root.jasmine, root.jQuery);
}
}((function() {return this; })(), function (window, jasmine, $) { "use strict";
jasmine.spiedEventsKey = function (selector, eventName) {
for(var i = 0;; i++) {
var key = [eventName, i].toString()
if (data.spiedEvents[key] === undefined || data.spiedEvents[key].selector.is($(selector))) {
return key
}
}
}
jasmine.getFixtures = function () {
return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures()
}
jasmine.getStyleFixtures = function () {
return jasmine.currentStyleFixtures_ = jasmine.currentStyleFixtures_ || new jasmine.StyleFixtures()
}
jasmine.Fixtures = function () {
this.containerId = 'jasmine-fixtures'
this.fixturesCache_ = {}
this.fixturesPath = 'spec/javascripts/fixtures'
}
jasmine.Fixtures.prototype.set = function (html) {
this.cleanUp()
return this.createContainer_(html)
}
jasmine.Fixtures.prototype.appendSet= function (html) {
this.addToContainer_(html)
}
jasmine.Fixtures.prototype.preload = function () {
this.read.apply(this, arguments)
}
jasmine.Fixtures.prototype.load = function () {
this.cleanUp()
this.createContainer_(this.read.apply(this, arguments))
}
jasmine.Fixtures.prototype.appendLoad = function () {
this.addToContainer_(this.read.apply(this, arguments))
}
jasmine.Fixtures.prototype.read = function () {
var htmlChunks = []
, fixtureUrls = arguments
for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]))
}
return htmlChunks.join('')
}
jasmine.Fixtures.prototype.clearCache = function () {
this.fixturesCache_ = {}
}
jasmine.Fixtures.prototype.cleanUp = function () {
$('#' + this.containerId).remove()
}
jasmine.Fixtures.prototype.sandbox = function (attributes) {
var attributesToSet = attributes || {}
return $('<div id="sandbox" />').attr(attributesToSet)
}
jasmine.Fixtures.prototype.createContainer_ = function (html) {
var container = $('<div>')
.attr('id', this.containerId)
.html(html)
$(document.body).append(container)
return container
}
jasmine.Fixtures.prototype.addToContainer_ = function (html){
var container = $(document.body).find('#'+this.containerId).append(html)
if (!container.length) {
this.createContainer_(html)
}
}
jasmine.Fixtures.prototype.getFixtureHtml_ = function (url) {
if (typeof this.fixturesCache_[url] === 'undefined') {
this.loadFixtureIntoCache_(url)
}
return this.fixturesCache_[url]
}
jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function (relativeUrl) {
var self = this
, url = this.makeFixtureUrl_(relativeUrl)
, htmlText = ''
, request = $.ajax({
async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
cache: false,
url: url,
dataType: 'html',
success: function (data, status, $xhr) {
htmlText = $xhr.responseText
}
}).fail(function ($xhr, status, err) {
throw new Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')')
})
var scripts = $($.parseHTML(htmlText, true)).find('script[src]') || [];
scripts.each(function(){
$.ajax({
async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
cache: false,
dataType: 'script',
url: $(this).attr('src'),
success: function (data, status, $xhr) {
htmlText += '<script>' + $xhr.responseText + '</script>'
},
error: function ($xhr, status, err) {
throw new Error('Script could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')')
}
});
})
self.fixturesCache_[relativeUrl] = htmlText;
}
jasmine.Fixtures.prototype.makeFixtureUrl_ = function (relativeUrl){
return this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl
}
jasmine.Fixtures.prototype.proxyCallTo_ = function (methodName, passedArguments) {
return this[methodName].apply(this, passedArguments)
}
jasmine.StyleFixtures = function () {
this.fixturesCache_ = {}
this.fixturesNodes_ = []
this.fixturesPath = 'spec/javascripts/fixtures'
}
jasmine.StyleFixtures.prototype.set = function (css) {
this.cleanUp()
this.createStyle_(css)
}
jasmine.StyleFixtures.prototype.appendSet = function (css) {
this.createStyle_(css)
}
jasmine.StyleFixtures.prototype.preload = function () {
this.read_.apply(this, arguments)
}
jasmine.StyleFixtures.prototype.load = function () {
this.cleanUp()
this.createStyle_(this.read_.apply(this, arguments))
}
jasmine.StyleFixtures.prototype.appendLoad = function () {
this.createStyle_(this.read_.apply(this, arguments))
}
jasmine.StyleFixtures.prototype.cleanUp = function () {
while(this.fixturesNodes_.length) {
this.fixturesNodes_.pop().remove()
}
}
jasmine.StyleFixtures.prototype.createStyle_ = function (html) {
var styleText = $('<div></div>').html(html).text()
, style = $('<style>' + styleText + '</style>')
this.fixturesNodes_.push(style)
$('head').append(style)
}
jasmine.StyleFixtures.prototype.clearCache = jasmine.Fixtures.prototype.clearCache
jasmine.StyleFixtures.prototype.read_ = jasmine.Fixtures.prototype.read
jasmine.StyleFixtures.prototype.getFixtureHtml_ = jasmine.Fixtures.prototype.getFixtureHtml_
jasmine.StyleFixtures.prototype.loadFixtureIntoCache_ = jasmine.Fixtures.prototype.loadFixtureIntoCache_
jasmine.StyleFixtures.prototype.makeFixtureUrl_ = jasmine.Fixtures.prototype.makeFixtureUrl_
jasmine.StyleFixtures.prototype.proxyCallTo_ = jasmine.Fixtures.prototype.proxyCallTo_
jasmine.getJSONFixtures = function () {
return jasmine.currentJSONFixtures_ = jasmine.currentJSONFixtures_ || new jasmine.JSONFixtures()
}
jasmine.JSONFixtures = function () {
this.fixturesCache_ = {}
this.fixturesPath = 'spec/javascripts/fixtures/json'
}
jasmine.JSONFixtures.prototype.load = function () {
this.read.apply(this, arguments)
return this.fixturesCache_
}
jasmine.JSONFixtures.prototype.read = function () {
var fixtureUrls = arguments
for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
this.getFixtureData_(fixtureUrls[urlIndex])
}
return this.fixturesCache_
}
jasmine.JSONFixtures.prototype.clearCache = function () {
this.fixturesCache_ = {}
}
jasmine.JSONFixtures.prototype.getFixtureData_ = function (url) {
if (!this.fixturesCache_[url]) this.loadFixtureIntoCache_(url)
return this.fixturesCache_[url]
}
jasmine.JSONFixtures.prototype.loadFixtureIntoCache_ = function (relativeUrl) {
var self = this
, url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl
$.ajax({
async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
cache: false,
dataType: 'json',
url: url,
success: function (data) {
self.fixturesCache_[relativeUrl] = data
},
error: function ($xhr, status, err) {
throw new Error('JSONFixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')')
}
})
}
jasmine.JSONFixtures.prototype.proxyCallTo_ = function (methodName, passedArguments) {
return this[methodName].apply(this, passedArguments)
}
jasmine.jQuery = function () {}
jasmine.jQuery.browserTagCaseIndependentHtml = function (html) {
return $('<div/>').append(html).html()
}
jasmine.jQuery.elementToString = function (element) {
return $(element).map(function () { return this.outerHTML; }).toArray().join(', ')
}
var data = {
spiedEvents: {}
, handlers: []
}
jasmine.jQuery.events = {
spyOn: function (selector, eventName) {
var handler = function (e) {
var calls = (typeof data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] !== 'undefined') ? data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)].calls : 0
data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] = {
selector: $(selector),
args: jasmine.util.argsToArray(arguments),
calls: ++calls
}
}
$(selector).on(eventName, handler)
data.handlers.push(handler)
return {
selector: selector,
eventName: eventName,
handler: handler,
reset: function (){
delete data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]
},
calls: {
count: function () {
return data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] ?
data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)].calls : 0;
},
any: function () {
return data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] ?
!!data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)].calls : false;
}
}
}
},
args: function (selector, eventName) {
var actualArgs = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)].args
if (!actualArgs) {
throw "There is no spy for " + eventName + " on " + selector.toString() + ". Make sure to create a spy using spyOnEvent."
}
return actualArgs
},
wasTriggered: function (selector, eventName) {
return !!(data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)])
},
wasTriggeredWith: function (selector, eventName, expectedArgs, util, customEqualityTesters) {
var actualArgs = jasmine.jQuery.events.args(selector, eventName).slice(1)
if (Object.prototype.toString.call(expectedArgs) !== '[object Array]')
actualArgs = actualArgs[0]
return util.equals(actualArgs, expectedArgs, customEqualityTesters)
},
wasPrevented: function (selector, eventName) {
var spiedEvent = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]
, args = (jasmine.util.isUndefined(spiedEvent)) ? {} : spiedEvent.args
, e = args ? args[0] : undefined
return e && e.isDefaultPrevented()
},
wasStopped: function (selector, eventName) {
var spiedEvent = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]
, args = (jasmine.util.isUndefined(spiedEvent)) ? {} : spiedEvent.args
, e = args ? args[0] : undefined
return e && e.isPropagationStopped()
},
cleanUp: function () {
data.spiedEvents = {}
data.handlers = []
}
}
var hasProperty = function (actualValue, expectedValue) {
if (expectedValue === undefined)
return actualValue !== undefined
return actualValue === expectedValue
}
beforeEach(function () {
jasmine.addMatchers({
toHaveClass: function () {
return {
compare: function (actual, className) {
return { pass: $(actual).hasClass(className) }
}
}
},
toHaveCss: function () {
return {
compare: function (actual, css) {
var stripCharsRegex = /[\s;\"\']/g
for (var prop in css) {
var value = css[prop]
// see issue #147 on gh
;if ((value === 'auto') && ($(actual).get(0).style[prop] === 'auto')) continue
var actualStripped = $(actual).css(prop).replace(stripCharsRegex, '')
var valueStripped = value.replace(stripCharsRegex, '')
if (actualStripped !== valueStripped) return { pass: false }
}
return { pass: true }
}
}
},
toBeVisible: function () {
return {
compare: function (actual) {
return { pass: $(actual).is(':visible') }
}
}
},
toBeHidden: function () {
return {
compare: function (actual) {
return { pass: $(actual).is(':hidden') }
}
}
},
toBeSelected: function () {
return {
compare: function (actual) {
return { pass: $(actual).is(':selected') }
}
}
},
toBeChecked: function () {
return {
compare: function (actual) {
return { pass: $(actual).is(':checked') }
}
}
},
toBeEmpty: function () {
return {
compare: function (actual) {
return { pass: $(actual).is(':empty') }
}
}
},
toBeInDOM: function () {
return {
compare: function (actual) {
return { pass: $.contains(document.documentElement, $(actual)[0]) }
}
}
},
toExist: function () {
return {
compare: function (actual) {
return { pass: $(actual).length }
}
}
},
toHaveLength: function () {
return {
compare: function (actual, length) {
return { pass: $(actual).length === length }
}
}
},
toHaveAttr: function () {
return {
compare: function (actual, attributeName, expectedAttributeValue) {
return { pass: hasProperty($(actual).attr(attributeName), expectedAttributeValue) }
}
}
},
toHaveProp: function () {
return {
compare: function (actual, propertyName, expectedPropertyValue) {
return { pass: hasProperty($(actual).prop(propertyName), expectedPropertyValue) }
}
}
},
toHaveId: function () {
return {
compare: function (actual, id) {
return { pass: $(actual).attr('id') == id }
}
}
},
toHaveHtml: function () {
return {
compare: function (actual, html) {
return { pass: $(actual).html() == jasmine.jQuery.browserTagCaseIndependentHtml(html) }
}
}
},
toContainHtml: function () {
return {
compare: function (actual, html) {
var actualHtml = $(actual).html()
, expectedHtml = jasmine.jQuery.browserTagCaseIndependentHtml(html)
return { pass: (actualHtml.indexOf(expectedHtml) >= 0) }
}
}
},
toHaveText: function () {
return {
compare: function (actual, text) {
var actualText = $(actual).text()
var trimmedText = $.trim(actualText)
if (text && $.isFunction(text.test)) {
return { pass: text.test(actualText) || text.test(trimmedText) }
} else {
return { pass: (actualText == text || trimmedText == text) }
}
}
}
},
toContainText: function () {
return {
compare: function (actual, text) {
var trimmedText = $.trim($(actual).text())
if (text && $.isFunction(text.test)) {
return { pass: text.test(trimmedText) }
} else {
return { pass: trimmedText.indexOf(text) != -1 }
}
}
}
},
toHaveValue: function () {
return {
compare: function (actual, value) {
return { pass: $(actual).val() === value }
}
}
},
toHaveData: function () {
return {
compare: function (actual, key, expectedValue) {
return { pass: hasProperty($(actual).data(key), expectedValue) }
}
}
},
toContainElement: function () {
return {
compare: function (actual, selector) {
return { pass: $(actual).find(selector).length }
}
}
},
toBeMatchedBy: function () {
return {
compare: function (actual, selector) {
return { pass: $(actual).filter(selector).length }
}
}
},
toBeDisabled: function () {
return {
compare: function (actual, selector) {
return { pass: $(actual).is(':disabled') }
}
}
},
toBeFocused: function (selector) {
return {
compare: function (actual, selector) {
return { pass: $(actual)[0] === $(actual)[0].ownerDocument.activeElement }
}
}
},
toHandle: function () {
return {
compare: function (actual, event) {
if ( !actual || actual.length === 0 ) return { pass: false };
var events = $._data($(actual).get(0), "events")
if (!events || !event || typeof event !== "string") {
return { pass: false }
}
var namespaces = event.split(".")
, eventType = namespaces.shift()
, sortedNamespaces = namespaces.slice(0).sort()
, namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)")
if (events[eventType] && namespaces.length) {
for (var i = 0; i < events[eventType].length; i++) {
var namespace = events[eventType][i].namespace
if (namespaceRegExp.test(namespace))
return { pass: true }
}
} else {
return { pass: (events[eventType] && events[eventType].length > 0) }
}
return { pass: false }
}
}
},
toHandleWith: function () {
return {
compare: function (actual, eventName, eventHandler) {
if ( !actual || actual.length === 0 ) return { pass: false };
var normalizedEventName = eventName.split('.')[0]
, stack = $._data($(actual).get(0), "events")[normalizedEventName]
for (var i = 0; i < stack.length; i++) {
if (stack[i].handler == eventHandler) return { pass: true }
}
return { pass: false }
}
}
},
toHaveBeenTriggeredOn: function () {
return {
compare: function (actual, selector) {
var result = { pass: jasmine.jQuery.events.wasTriggered(selector, actual) }
result.message = result.pass ?
"Expected event " + $(actual) + " not to have been triggered on " + selector :
"Expected event " + $(actual) + " to have been triggered on " + selector
return result;
}
}
},
toHaveBeenTriggered: function (){
return {
compare: function (actual) {
var eventName = actual.eventName
, selector = actual.selector
, result = { pass: jasmine.jQuery.events.wasTriggered(selector, eventName) }
result.message = result.pass ?
"Expected event " + eventName + " not to have been triggered on " + selector :
"Expected event " + eventName + " to have been triggered on " + selector
return result
}
}
},
toHaveBeenTriggeredOnAndWith: function (j$, customEqualityTesters) {
return {
compare: function (actual, selector, expectedArgs) {
var wasTriggered = jasmine.jQuery.events.wasTriggered(selector, actual)
, result = { pass: wasTriggered && jasmine.jQuery.events.wasTriggeredWith(selector, actual, expectedArgs, j$, customEqualityTesters) }
if (wasTriggered) {
var actualArgs = jasmine.jQuery.events.args(selector, actual, expectedArgs)[1]
result.message = result.pass ?
"Expected event " + actual + " not to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs) :
"Expected event " + actual + " to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs)
} else {
// todo check on this
result.message = result.pass ?
"Expected event " + actual + " not to have been triggered on " + selector :
"Expected event " + actual + " to have been triggered on " + selector
}
return result
}
}
},
toHaveBeenPreventedOn: function () {
return {
compare: function (actual, selector) {
var result = { pass: jasmine.jQuery.events.wasPrevented(selector, actual) }
result.message = result.pass ?
"Expected event " + actual + " not to have been prevented on " + selector :
"Expected event " + actual + " to have been prevented on " + selector
return result
}
}
},
toHaveBeenPrevented: function () {
return {
compare: function (actual) {
var eventName = actual.eventName
, selector = actual.selector
, result = { pass: jasmine.jQuery.events.wasPrevented(selector, eventName) }
result.message = result.pass ?
"Expected event " + eventName + " not to have been prevented on " + selector :
"Expected event " + eventName + " to have been prevented on " + selector
return result
}
}
},
toHaveBeenStoppedOn: function () {
return {
compare: function (actual, selector) {
var result = { pass: jasmine.jQuery.events.wasStopped(selector, actual) }
result.message = result.pass ?
"Expected event " + actual + " not to have been stopped on " + selector :
"Expected event " + actual + " to have been stopped on " + selector
return result;
}
}
},
toHaveBeenStopped: function () {
return {
compare: function (actual) {
var eventName = actual.eventName
, selector = actual.selector
, result = { pass: jasmine.jQuery.events.wasStopped(selector, eventName) }
result.message = result.pass ?
"Expected event " + eventName + " not to have been stopped on " + selector :
"Expected event " + eventName + " to have been stopped on " + selector
return result
}
}
}
})
jasmine.getEnv().addCustomEqualityTester(function(a, b) {
if (a && b) {
if (a instanceof $ || jasmine.isDomNode(a)) {
var $a = $(a)
if (b instanceof $)
return $a.length == b.length && a.is(b)
return $a.is(b);
}
if (b instanceof $ || jasmine.isDomNode(b)) {
var $b = $(b)
if (a instanceof $)
return a.length == $b.length && $b.is(a)
return $(b).is(a);
}
}
})
jasmine.getEnv().addCustomEqualityTester(function (a, b) {
if (a instanceof $ && b instanceof $ && a.size() == b.size())
return a.is(b)
})
})
afterEach(function () {
jasmine.getFixtures().cleanUp()
jasmine.getStyleFixtures().cleanUp()
jasmine.jQuery.events.cleanUp()
})
window.readFixtures = function () {
return jasmine.getFixtures().proxyCallTo_('read', arguments)
}
window.preloadFixtures = function () {
jasmine.getFixtures().proxyCallTo_('preload', arguments)
}
window.loadFixtures = function () {
jasmine.getFixtures().proxyCallTo_('load', arguments)
}
window.appendLoadFixtures = function () {
jasmine.getFixtures().proxyCallTo_('appendLoad', arguments)
}
window.setFixtures = function (html) {
return jasmine.getFixtures().proxyCallTo_('set', arguments)
}
window.appendSetFixtures = function () {
jasmine.getFixtures().proxyCallTo_('appendSet', arguments)
}
window.sandbox = function (attributes) {
return jasmine.getFixtures().sandbox(attributes)
}
window.spyOnEvent = function (selector, eventName) {
return jasmine.jQuery.events.spyOn(selector, eventName)
}
window.preloadStyleFixtures = function () {
jasmine.getStyleFixtures().proxyCallTo_('preload', arguments)
}
window.loadStyleFixtures = function () {
jasmine.getStyleFixtures().proxyCallTo_('load', arguments)
}
window.appendLoadStyleFixtures = function () {
jasmine.getStyleFixtures().proxyCallTo_('appendLoad', arguments)
}
window.setStyleFixtures = function (html) {
jasmine.getStyleFixtures().proxyCallTo_('set', arguments)
}
window.appendSetStyleFixtures = function (html) {
jasmine.getStyleFixtures().proxyCallTo_('appendSet', arguments)
}
window.loadJSONFixtures = function () {
return jasmine.getJSONFixtures().proxyCallTo_('load', arguments)
}
window.getJSONFixture = function (url) {
return jasmine.getJSONFixtures().proxyCallTo_('read', arguments)[url]
}
}));

621
yarn.lock

File diff suppressed because it is too large Load diff