Commit 59c81fd7 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'vs/remove-karma' into 'master'

Remove Karma and anything related

See merge request gitlab-org/gitlab!68977
parents 7a38be8b bd710a55
......@@ -36,6 +36,5 @@ exclude_paths:
- webpack-report/
- log/
- backups/
- coverage-javascript/
- plugins/
- file_hooks/
......@@ -2,7 +2,6 @@
/builds/
/coverage/
/coverage-frontend/
/coverage-javascript/
/node_modules/
/public/
/tmp/
......
......@@ -50,7 +50,6 @@ eslint-report.html
/config/sidekiq.yml
/config/registry.key
/coverage/*
/coverage-javascript/
/db/*.sqlite3
/db/*.sqlite3-journal
/db/data.yml
......
......@@ -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
......
......@@ -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:
......
......@@ -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
......
......@@ -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`
......
......@@ -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'],
},
]);
}
......
/* 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);
};
......@@ -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'),
});
......
# 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
......@@ -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.
......
......@@ -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
......
......@@ -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)
......
......@@ -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:
......
......@@ -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);
}
# 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
......@@ -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",
......
......@@ -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
......
#!/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
......@@ -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) => {
......
......@@ -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 = {})
......
---
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
// 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();
});
});
});
export * from '../../../frontend/lib/utils/mock_data';
/* 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?');
}
});
});
export * from '../frontend/__helpers__/test_constants';
......@@ -60,7 +60,6 @@ RSpec.describe Tooling::Danger::ProjectHelper do
'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 @@ RSpec.describe Tooling::Danger::ProjectHelper do
'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 @@ RSpec.describe Tooling::Danger::ProjectHelper do
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
......
......@@ -10,7 +10,6 @@ module Tooling
duplicate_yarn_dependencies
eslint
gitaly
karma
pajamas
pipeline
prettier
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment