Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-07-10 03:31:58 +00:00
parent bcbc9e0174
commit 5bbbda77c8
19 changed files with 684 additions and 64 deletions

View File

@ -0,0 +1,37 @@
import GroupItem from './group_item.vue';
export default {
component: GroupItem,
title: 'vue_shared/list_selector/group_item',
};
const Template = (args, { argTypes }) => ({
components: { GroupItem },
props: Object.keys(argTypes),
template: '<group-item v-bind="$props" />',
});
export const Default = Template.bind({});
Default.args = {
data: {
id: '1',
fullName: 'Gitlab Org',
name: 'Gitlab Org',
},
canDelete: false,
};
export const DeletableGroup = Template.bind({});
DeletableGroup.args = {
...Default.args,
canDelete: true,
};
export const HiddenGroups = Template.bind({});
HiddenGroups.args = {
...Default.args,
data: {
...Default.args.data,
type: 'hidden_groups',
},
};

View File

@ -44,9 +44,9 @@ export default {
</script>
<template>
<span class="gl-display-flex gl-align-items-center gl-gap-3">
<hidden-groups-item v-if="isHiddenGroups" class="gl-flex-grow-1" />
<div v-else class="gl-display-flex gl-align-items-center gl-gap-2 gl-flex-grow-1">
<div class="gl-flex gl-items-center gl-gap-3">
<hidden-groups-item v-if="isHiddenGroups" class="gl-grow" />
<div v-else class="gl-flex gl-items-center gl-gap-3 gl-grow">
<gl-avatar
:alt="fullName"
:entity-name="fullName"
@ -54,7 +54,7 @@ export default {
:src="avatarUrl"
fallback-on-error
/>
<span class="gl-display-flex gl-flex-direction-column">
<span class="gl-flex gl-flex-col">
<span class="gl-font-bold">{{ fullName }}</span>
<span class="gl-text-gray-600">@{{ name }}</span>
</span>
@ -68,5 +68,5 @@ export default {
category="tertiary"
@click="$emit('delete', data.id)"
/>
</span>
</div>
</template>

View File

@ -1,7 +1,9 @@
---
migration_job_name: CreateComplianceStandardsAdherence
description: This migration creates 'project_compliance_standards_adherence' table for existing projects
description: This migration creates 'project_compliance_standards_adherence' table
for existing projects
feature_category: compliance_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129941
queued_migration_version: 20230818142801
milestone: '16.4'
finalized_by: '20240707231815'

View File

@ -8,4 +8,5 @@ description: Geo event for when the repositories for selective sync of a specifi
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/312bc703a4619b87ba2ac4e59623e7747a24502c
milestone: '9.5'
gitlab_schema: gitlab_main_cell
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/464364 # this table will be deleted soon
removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158660
removed_in_milestone: '17.2'

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeCreateComplianceStandardsAdherence < Gitlab::Database::Migration[2.2]
milestone '17.2'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'CreateComplianceStandardsAdherence',
table_name: :projects,
column_name: :id,
job_arguments: [],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
class RemoveForeignKeyGeoEventLog < Gitlab::Database::Migration[2.2]
milestone '17.2'
disable_ddl_transaction!
FROM_TABLE = :geo_event_log
TO_TABLE = :geo_repositories_changed_events
def up
with_lock_retries do
remove_foreign_key(
FROM_TABLE,
TO_TABLE,
column: :repositories_changed_event_id,
if_exists: true
)
end
end
def down
add_concurrent_foreign_key(
FROM_TABLE,
TO_TABLE,
name: :fk_4a99ebfd60,
column: :repositories_changed_event_id,
on_delete: :cascade
)
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class DropGeoRepositoriesChangedEvents < Gitlab::Database::Migration[2.2]
milestone '17.2'
disable_ddl_transaction!
def up
drop_table :geo_repositories_changed_events
end
def down
create_table :geo_repositories_changed_events do |t|
t.integer :geo_node_id,
index: { name: 'index_geo_repositories_changed_events_on_geo_node_id' },
null: false
end
add_concurrent_foreign_key(
:geo_repositories_changed_events,
:geo_nodes,
name: :fk_rails_75ec0fefcc,
column: :geo_node_id,
on_delete: :cascade
)
end
end

View File

@ -0,0 +1 @@
274a6acea5c5940bd76616e7bd9c6e7c1d58f1ca133f56c00b930bdf094d85a3

View File

@ -0,0 +1 @@
a500e7c16e274cd083b865dea60cafe1bd9618f8f5767f492947b7b43d7e9284

View File

@ -0,0 +1 @@
23a8c778820f8a25cdbe7c864d3fa9b667a11d6b61e3d9e6ad2f5e7b32da93b9

View File

@ -10695,20 +10695,6 @@ CREATE SEQUENCE geo_nodes_id_seq
ALTER SEQUENCE geo_nodes_id_seq OWNED BY geo_nodes.id;
CREATE TABLE geo_repositories_changed_events (
id bigint NOT NULL,
geo_node_id integer NOT NULL
);
CREATE SEQUENCE geo_repositories_changed_events_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE geo_repositories_changed_events_id_seq OWNED BY geo_repositories_changed_events.id;
CREATE TABLE ghost_user_migrations (
id bigint NOT NULL,
user_id bigint NOT NULL,
@ -20864,8 +20850,6 @@ ALTER TABLE ONLY geo_node_statuses ALTER COLUMN id SET DEFAULT nextval('geo_node
ALTER TABLE ONLY geo_nodes ALTER COLUMN id SET DEFAULT nextval('geo_nodes_id_seq'::regclass);
ALTER TABLE ONLY geo_repositories_changed_events ALTER COLUMN id SET DEFAULT nextval('geo_repositories_changed_events_id_seq'::regclass);
ALTER TABLE ONLY ghost_user_migrations ALTER COLUMN id SET DEFAULT nextval('ghost_user_migrations_id_seq'::regclass);
ALTER TABLE ONLY gitlab_subscription_histories ALTER COLUMN id SET DEFAULT nextval('gitlab_subscription_histories_id_seq'::regclass);
@ -23018,9 +23002,6 @@ ALTER TABLE ONLY geo_node_statuses
ALTER TABLE ONLY geo_nodes
ADD CONSTRAINT geo_nodes_pkey PRIMARY KEY (id);
ALTER TABLE ONLY geo_repositories_changed_events
ADD CONSTRAINT geo_repositories_changed_events_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ghost_user_migrations
ADD CONSTRAINT ghost_user_migrations_pkey PRIMARY KEY (id);
@ -27289,8 +27270,6 @@ CREATE UNIQUE INDEX index_geo_nodes_on_name ON geo_nodes USING btree (name);
CREATE INDEX index_geo_nodes_on_primary ON geo_nodes USING btree ("primary");
CREATE INDEX index_geo_repositories_changed_events_on_geo_node_id ON geo_repositories_changed_events USING btree (geo_node_id);
CREATE INDEX index_ghost_user_migrations_on_consume_after_id ON ghost_user_migrations USING btree (consume_after, id);
CREATE UNIQUE INDEX index_ghost_user_migrations_on_user_id ON ghost_user_migrations USING btree (user_id);
@ -32254,9 +32233,6 @@ ALTER TABLE ONLY releases
ALTER TABLE ONLY workspace_variables
ADD CONSTRAINT fk_494e093520 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY geo_event_log
ADD CONSTRAINT fk_4a99ebfd60 FOREIGN KEY (repositories_changed_event_id) REFERENCES geo_repositories_changed_events(id) ON DELETE CASCADE;
ALTER TABLE ONLY user_namespace_callouts
ADD CONSTRAINT fk_4b1257f385 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
@ -34237,9 +34213,6 @@ ALTER TABLE ONLY release_links
ALTER TABLE ONLY milestone_releases
ADD CONSTRAINT fk_rails_754f27dbfa FOREIGN KEY (release_id) REFERENCES releases(id) ON DELETE CASCADE;
ALTER TABLE ONLY geo_repositories_changed_events
ADD CONSTRAINT fk_rails_75ec0fefcc FOREIGN KEY (geo_node_id) REFERENCES geo_nodes(id) ON DELETE CASCADE;
ALTER TABLE ONLY resource_label_events
ADD CONSTRAINT fk_rails_75efb0a653 FOREIGN KEY (epic_id) REFERENCES epics(id) ON DELETE CASCADE;

View File

@ -551,3 +551,51 @@ If either of these values is set to `on`, you must disable it:
1. Restart your Postgres server to apply these settings.
1. Try to [apply schema migrations](#apply-schema-migrations) again, if applicable.
1. Restart the registry `sudo gitlab-ctl restart registry`.
### Error: `cannot import all repositories while the tags table has entries`
If you try to [migrate existing registries](#existing-registries) and encounter the following error:
```shell
ERRO[0000] cannot import all repositories while the tags table has entries, you must truncate the table manually before retrying,
see https://docs.gitlab.com/ee/administration/packages/container_registry_metadata_database.html#troubleshooting
common_blobs=true dry_run=false error="tags table is not empty"
```
This error happens when there are existing entries in the `tags` table of the registry database,
which can happen if you:
- Attempted the [one step migration](#one-step-migration) and encountered errors.
- Attempted the [three-step migration](#three-step-migration) process and encountered errors.
- Stopped the migration process on purpose.
- Tried to run the migration again after any of the above.
- Ran the migration against the wrong configuration file.
To resolve this issue, you must delete the existing entries in the tags table.
You must truncate the table manually on your PostgreSQL instance:
1. Edit `/etc/gitlab/gitlab.rb` and ensure the metadata database is **disabled**:
```ruby
registry['database'] = {
'enabled' => false,
'host' => 'localhost',
'port' => 5432,
'user' => 'registry-database-user',
'password' => 'registry-database-password',
'dbname' => 'registry-database-name',
'sslmode' => 'require', # See the PostgreSQL documentation for additional information https://www.postgresql.org/docs/current/libpq-ssl.html.
'sslcert' => '/path/to/cert.pem',
'sslkey' => '/path/to/private.key',
'sslrootcert' => '/path/to/ca.pem'
}
```
1. Connect to your registry database using a PostgreSQL client.
1. Truncate the `tags` table to remove all existing entries:
```sql
TRUNCATE TABLE tags RESTART IDENTITY CASCADE;
```
1. After truncating the `tags` table, try running the migration process again.

View File

@ -769,6 +769,54 @@ beforeEach(() => {
});
```
## Console warnings and errors in tests
Unexpected console warnings and errors are indicative of problems in our production code.
We want our test environment to be strict, so your tests should fail when unexpected
`console.error` or `console.warn` calls are made.
### Ignoring console messages from watcher
Since there's a lot of code outside of our control, there are some console messages that
are ignored by default and will **not** fail a test if used. This list of ignored
messages can be maintained where we call `setupConsoleWatcher`. Example:
```javascript
setupConsoleWatcher({
ignores: [
...,
// Any call to `console.error('Foo bar')` or `console.warn('Foo bar')` will be ignored by our console watcher.
'Foo bar',
// Use regex to allow for flexible message matching.
/Lorem ipsum/,
]
});
```
If a specific test needs to ignore a specific message for a `describe` block, use the
`ignoreConsoleMessages` helper near the top of the `describe`. This automatically
calls `beforeAll` and `afterAll` to set up/teardown this set of ignored for the test
context.
Use this sparingly and only if absolutely necessary for test maintainability. Example:
```javascript
import { ignoreConsoleMessages } from 'helpers/console_watcher';
describe('foos/components/foo.vue', () => {
describe('when blooped', () => {
// Will not fail a test if `console.warn('Lorem ipsum')` is called
ignoreConsoleMessages([
/^Lorem ipsum/
]);
});
describe('default', () => {
// Will fail a test if `console.warn('Lorem ipsum')` is called
});
});
```
## Factories
TBU

View File

@ -46490,12 +46490,21 @@ msgstr ""
msgid "ScanExecutionPolicy|Add new condition"
msgstr ""
msgid "ScanExecutionPolicy|Are you sure you want to create merge request fot this policy?"
msgstr ""
msgid "ScanExecutionPolicy|Back to edit policy"
msgstr ""
msgid "ScanExecutionPolicy|Choose a method to execute code"
msgstr ""
msgid "ScanExecutionPolicy|Conditions"
msgstr ""
msgid "ScanExecutionPolicy|Create merge request"
msgstr ""
msgid "ScanExecutionPolicy|Create new scan profile"
msgstr ""
@ -46547,6 +46556,9 @@ msgstr ""
msgid "ScanExecutionPolicy|Override"
msgstr ""
msgid "ScanExecutionPolicy|Potential overload for infrastructure"
msgstr ""
msgid "ScanExecutionPolicy|Run %{scan} with the following options:"
msgstr ""
@ -46610,6 +46622,9 @@ msgstr ""
msgid "ScanExecutionPolicy|The file path can't be empty"
msgstr ""
msgid "ScanExecutionPolicy|This scan execution policy will generate a large number of pipelines, which can have a significant performance impact. To reduce potential performance issues, consider creating separate policies for smaller subsets of projects."
msgstr ""
msgid "ScanExecutionPolicy|Triggers:"
msgstr ""

View File

@ -96,7 +96,7 @@ RSpec.describe 'Database schema', feature_category: :database do
epics: %w[updated_by_id last_edited_by_id state_id],
events: %w[target_id],
forked_project_links: %w[forked_from_project_id],
geo_event_log: %w[hashed_storage_attachments_event_id],
geo_event_log: %w[hashed_storage_attachments_event_id repositories_changed_event_id],
geo_node_statuses: %w[last_event_id cursor_last_event_id],
geo_nodes: %w[oauth_application_id],
geo_repository_deleted_events: %w[project_id],

View File

@ -0,0 +1,244 @@
const METHODS_TO_WATCH = ['error', 'warn'];
const matchesStringOrRegex = (target, strOrRegex) => {
if (typeof strOrRegex === 'string') {
return target === strOrRegex;
}
// why: We can't just check `instanceof RegExp` for some reason. I think it happens when values cross the Jest test sandbox into the outer environment.
// Please see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145065#note_1788386920
if ('test' in strOrRegex) {
return strOrRegex.test(target);
}
throw new Error(`Unexpected value to match (${strOrRegex}). Expected string or RegExp.`);
};
export const throwErrorFromCalls = (consoleCalls) => {
const consoleCallsList = consoleCalls
.map(({ method, args }, idx) => `\n[${idx + 1}] ${method}: ${args}\n`)
.join('')
.split('\n')
.map((x) => `\t${x}`)
.join('\n');
throw new Error(
`Unexpected calls to console (${consoleCalls.length}) with:\n${consoleCallsList}\n`,
);
};
class ConsoleWatcher {
/**
* Reference to the console instance that we are watching and overriding.
*
* @type {Console}
*/
#console;
/**
* Array of RegExp's or string's that will be used to ignore certain calls.
* These are applied to only the message received by the `ConsoleWatcher`, regardless
* of whether it was a `console.warn` or `console.error`.
*
* @type {(RegExp | string)[]}
*/
#ignores;
/**
* List of console method calls that were collected. This can include ignored consoles.
* We don't filter out ignores until we `getCalls`.
*
* @type {{method: string, args: unknown[]}[]}
*/
#calls;
/**
* @type {{ error: Function, warn: Function }} Reference to the original Console methods
*/
#original;
/**
* Flag for whether to use the legacy behavior of throwing immediately
*
* @type {boolean}
*/
#shouldThrowImmediately;
constructor(console, { ignores = [], shouldThrowImmediately = false } = {}) {
this.#console = console;
this.#ignores = ignores;
this.#calls = [];
this.#original = {};
this.#shouldThrowImmediately = shouldThrowImmediately;
METHODS_TO_WATCH.forEach((method) => {
const originalFn = console[method];
this.#original[method] = originalFn;
Object.assign(console, {
[method]: (...args) => {
this.#handleCall(method, args);
},
});
});
}
dispose() {
Object.entries(this.#original).forEach(([key, fn]) => {
Object.assign(this.#console, { [key]: fn });
});
}
getIgnores() {
return this.#ignores;
}
setIgnores(ignores) {
this.#ignores = ignores;
}
setShouldThrowImmediately(value) {
this.#shouldThrowImmediately = value;
}
shouldThrowImmediately() {
return this.#shouldThrowImmediately;
}
forgetCalls() {
this.#calls = [];
}
getCalls() {
return this.#calls.filter((call) => !this.#shouldIgnore(call));
}
#shouldIgnore({ args }) {
const argsAsStr = args.map(String).join();
return this.#ignores.some((ignoreMatcher) => matchesStringOrRegex(argsAsStr, ignoreMatcher));
}
#handleCall(method, args) {
const call = { method, args };
if (this.#shouldThrowImmediately && !this.#shouldIgnore(call)) {
throwErrorFromCalls([call]);
return;
}
this.#calls.push(call);
this.#original[method](...args);
}
}
/**
* @param {CustomEnvironment} environment - Jest environment to attach the globals to
* @param {Console} console - the instnace of Console to setup watchers.
* @param {Object} options - optional options to use when setting up the console watcher.
* @param {(RegExp | string)[]} options.ignores - list of console messages to ignore.
* @param {boolean} options.shouldThrowImmediately - flag for whether we do the legacy behavior of throwing immediately.
* @returns
*/
export const setupConsoleWatcher = (environment, console, options) => {
if (environment.global.jestConsoleWatcher) {
throw new Error('jestConsoleWatcher already exists');
}
const consoleWatcher = new ConsoleWatcher(console, options);
// eslint-disable-next-line no-param-reassign
environment.global.jestConsoleWatcher = consoleWatcher;
return consoleWatcher;
};
export const forgetConsoleCalls = () => global.jestConsoleWatcher?.forgetCalls();
export const getConsoleCalls = () => global.jestConsoleWatcher?.getCalls() || [];
/**
* Flags whether or the current `describe` should adopt the legacy test behavior of throwing immediately on `console.warn` or `console.error`
*
* Example:
*
* ```javascript
* describe('Foo', () => {
* useConsoleWatcherThrowsImmediately();
*
* describe('bar', () => {
* useConsoleWatcherThrowsImmediately(false);
*
* // These tests **will not** throw immediately if `console.warn` or `console.error` is called.
* });
*
* describe('zed', () => {
* // These tests **will** throw immediately if `console.warn` or `console.error` is called.
* })
* });
* ```
*
* @param {boolean} val - True if the consoleWatcher should throw immediately on a console method call
* @deprecated This only exists to support legacy tests that depend on this erroneous test behavior
*/
export const useConsoleWatcherThrowsImmediately = (val = true) => {
let origLegacy;
beforeAll(() => {
origLegacy = global.jestConsoleWatcher.shouldThrowImmediately();
global.jestConsoleWatcher.setShouldThrowImmediately(val);
});
afterAll(() => {
global.jestConsoleWatcher.setShouldThrowImmediately(origLegacy);
});
};
/**
* Sets up the console watcher to ignore the given messages for the current `describe` block.
*
* Example:
*
* ```javascript
* describe('Foo', () => {
* ignoreConsoleMessages([
* 'Hello world',
* /The field .* is not okay/,
* ]);
*
* it('works', () => {
* // Passes :)
* console.error('Hello world');
* console.warn('The field FOO is not okay');
*
* // Fail :(
* console.error('Hello world, strings are compared strictly.');
* });
* });
* ```
*
* @param {(string | RegExp)[]} ignores - Array of console messages to ignore for the current `describe` block.
*/
export const ignoreConsoleMessages = (ignores) => {
if (!Array.isArray(ignores)) {
throw new Error('Expected ignoreConsoleMessages to receive an Array of strings or RegExp');
}
let origIgnores;
beforeAll(() => {
origIgnores = global.jestConsoleWatcher.getIgnores();
global.jestConsoleWatcher.setIgnores(origIgnores.concat(ignores));
});
afterAll(() => {
global.jestConsoleWatcher.setIgnores(origIgnores);
});
};
export const ignoreVueConsoleWarnings = () =>
ignoreConsoleMessages([/^\[Vue warn\]: Missing required prop/, /^\[Vue warn\]: Invalid prop/]);

View File

@ -0,0 +1,175 @@
import {
setupConsoleWatcher,
throwErrorFromCalls,
forgetConsoleCalls,
getConsoleCalls,
ignoreConsoleMessages,
// eslint-disable-next-line import/no-deprecated
useConsoleWatcherThrowsImmediately,
} from './console_watcher';
const TEST_IGNORED_MESSAGE = 'Full message to ignore.';
const TEST_IGNORED_REGEX_MESSAGE = 'Part of this message matches partial ignore.';
describe('__helpers__/console_watcher', () => {
let testEnvironment;
let testConsole;
let testConsoleOriginalFn;
let consoleWatcher;
const callConsoleMethods = () => {
testConsole.log('Hello world log');
testConsole.info('Hello world info');
testConsole.info(TEST_IGNORED_MESSAGE);
testConsole.warn(TEST_IGNORED_REGEX_MESSAGE);
testConsole.warn('Hello world warn');
testConsole.error('Hello world error');
testConsole.error(TEST_IGNORED_MESSAGE);
};
// note: To test the beforeAll/afterAll behavior in some parts of console_watcher, we need to have our setup
// use beforeAll/afterAll instead of beforeEach/afterEach.
beforeAll(() => {
testEnvironment = { global: {} };
testConsole = {
log: (...args) => testConsoleOriginalFn('log', ...args),
info: (...args) => testConsoleOriginalFn('info', ...args),
warn: (...args) => testConsoleOriginalFn('warn', ...args),
error: (...args) => testConsoleOriginalFn('error', ...args),
};
Object.defineProperty(global, 'jestConsoleWatcher', {
get() {
return testEnvironment.global.jestConsoleWatcher;
},
});
});
beforeEach(() => {
// why: Let's make sure we have a fresh spy for every test
testConsoleOriginalFn = jest.fn();
});
afterEach(() => {
// why: We need to forget calls or else our main test_setup will pick up on console calls and throw an error
forgetConsoleCalls();
});
describe('throwErrorFromCalls', () => {
it('throws error with message containing calls', () => {
const calls = [
{ method: 'error', args: ['Hello world', 2, 'Lorem\nIpsum\nDolar\nSit'] },
{ method: 'info', args: [] },
{ method: 'warn', args: ['Hello world', 'something bad happened'] },
];
expect(() => throwErrorFromCalls(calls)).toThrowErrorMatchingInlineSnapshot(`
"Unexpected calls to console (3) with:
[1] error: Hello world,2,Lorem
Ipsum
Dolar
Sit
[2] info:
[3] warn: Hello world,something bad happened
"
`);
});
});
describe('setupConsoleWatcher', () => {
beforeAll(() => {
testEnvironment = { global: {} };
consoleWatcher = setupConsoleWatcher(testEnvironment, testConsole, {
ignores: ['Full message to ignore.', /partial ignore/],
});
});
afterAll(() => {
consoleWatcher.dispose();
});
describe.each(['warn', 'error'])('with %s', (method) => {
it('with unexpected message, calls original console method', () => {
testConsole[method]('BOOM!');
expect(testConsoleOriginalFn).toHaveBeenCalledTimes(1);
expect(testConsoleOriginalFn).toHaveBeenCalledWith(method, 'BOOM!');
});
it('with ignored message, calls original console method', () => {
testConsole[method](TEST_IGNORED_MESSAGE);
expect(testConsoleOriginalFn).toHaveBeenCalledTimes(1);
expect(testConsoleOriginalFn).toHaveBeenCalledWith(method, TEST_IGNORED_MESSAGE);
});
});
describe('with ignoreConsoleMessages', () => {
ignoreConsoleMessages([/Hello world .*/]);
it('adds to ignored messages only for describe block', () => {
callConsoleMethods();
expect(getConsoleCalls()).toEqual([]);
});
});
describe('with useConsoleWatcherThrowsImmediately', () => {
// eslint-disable-next-line import/no-deprecated
useConsoleWatcherThrowsImmediately();
it('throws when non ignored message', () => {
expect(callConsoleMethods).toThrow();
});
});
it('with getConsoleCalls, only returns non ignored ones', () => {
expect(getConsoleCalls()).toEqual([]);
callConsoleMethods();
expect(getConsoleCalls()).toEqual([
{ method: 'warn', args: ['Hello world warn'] },
{ method: 'error', args: ['Hello world error'] },
]);
});
it('with forgetConsoleCalls, clears out calls', () => {
callConsoleMethods();
forgetConsoleCalls();
expect(getConsoleCalls()).toEqual([]);
});
});
describe('setupConsoleWatcher with shouldThrowImmediately', () => {
beforeAll(() => {
testEnvironment = { global: {} };
consoleWatcher = setupConsoleWatcher(testEnvironment, testConsole, {
ignores: ['Full message to ignore.', /partial ignore/],
shouldThrowImmediately: true,
});
});
afterAll(() => {
consoleWatcher.dispose();
});
it('does not throw on ignored call', () => {
expect(() => testConsole.error(TEST_IGNORED_MESSAGE)).not.toThrow();
});
it('throws when call is not ignored', () => {
expect(() => testConsole.error('BLOW UP!')).toThrowErrorMatchingInlineSnapshot(`
"Unexpected calls to console (1) with:
[1] error: BLOW UP!
"
`);
});
});
});

View File

@ -8,6 +8,7 @@ const {
} = require('./__helpers__/fake_date/fake_date');
const { TEST_HOST } = require('./__helpers__/test_constants');
const { createGon } = require('./__helpers__/gon_helper');
const { setupConsoleWatcher } = require('./__helpers__/console_watcher');
class CustomEnvironment extends TestEnvironment {
constructor({ globalConfig, projectConfig }, context) {
@ -18,35 +19,17 @@ class CustomEnvironment extends TestEnvironment {
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39496#note_503084332
setGlobalDateToFakeDate();
const { error: originalErrorFn } = context.console;
Object.assign(context.console, {
error(...args) {
const firstError = args?.[0];
if (
typeof firstError === 'string' &&
['[Vue warn]: Missing required prop', '[Vue warn]: Invalid prop'].some((line) =>
firstError.startsWith(line),
)
) {
originalErrorFn.apply(context.console, args);
return;
}
throw new ErrorWithStack(
`Unexpected call of console.error() with:\n\n${args.join(', ')}`,
this.error,
);
},
warn(...args) {
if (args?.[0]?.includes('The updateQuery callback for fetchMore is deprecated')) {
return;
}
throw new ErrorWithStack(
`Unexpected call of console.warn() with:\n\n${args.join(', ')}`,
this.warn,
);
},
this.jestConsoleWatcher = setupConsoleWatcher(this, context.console, {
ignores: [
/The updateQuery callback for fetchMore is deprecated/,
// TODO: Remove this and replace with localized calls to `ignoreVueConsoleWarnings`
// https://gitlab.com/gitlab-org/gitlab/-/issues/396779#note_1788506238
/^\[Vue warn\]: Missing required prop/,
/^\[Vue warn\]: Invalid prop/,
],
// TODO: Remove this and replace with localized calls to `useConsoleWatcherThrowsImmediately`
// https://gitlab.com/gitlab-org/gitlab/-/issues/396779#note_1788506238
shouldThrowImmediately: true,
});
const { IS_EE } = projectConfig.testEnvironmentOptions;
@ -123,6 +106,8 @@ class CustomEnvironment extends TestEnvironment {
// Reset `Date` so that Jest can report timing accurately *roll eyes*...
setGlobalDateToRealDate();
this.jestConsoleWatcher.dispose();
// eslint-disable-next-line no-restricted-syntax
await new Promise(setImmediate);

View File

@ -4,6 +4,7 @@ import { setImmediate } from 'timers';
import Dexie from 'dexie';
import { IDBKeyRange, IDBFactory } from 'fake-indexeddb';
import 'helpers/shared_test_setup';
import { forgetConsoleCalls, getConsoleCalls, throwErrorFromCalls } from 'helpers/console_watcher';
const indexedDB = new IDBFactory();
@ -19,6 +20,15 @@ afterEach(() =>
}),
);
afterEach(() => {
const consoleCalls = getConsoleCalls();
forgetConsoleCalls();
if (consoleCalls.length) {
throwErrorFromCalls(consoleCalls);
}
});
afterEach(async () => {
const dbs = await indexedDB.databases();