Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
bcbc9e0174
commit
5bbbda77c8
|
|
@ -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',
|
||||
},
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
274a6acea5c5940bd76616e7bd9c6e7c1d58f1ca133f56c00b930bdf094d85a3
|
||||
|
|
@ -0,0 +1 @@
|
|||
a500e7c16e274cd083b865dea60cafe1bd9618f8f5767f492947b7b43d7e9284
|
||||
|
|
@ -0,0 +1 @@
|
|||
23a8c778820f8a25cdbe7c864d3fa9b667a11d6b61e3d9e6ad2f5e7b32da93b9
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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/]);
|
||||
|
|
@ -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!
|
||||
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue