+
+ {{ s__('TimeTracking|Spent') }}
{{ timeSpentHumanReadable }}
-
-
{{ s__('TimeTrackingEstimated|Est') }}
+
+ {{ s__('TimeTrackingEstimated|Est') }}
{{ timeEstimateHumanReadable }}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 099dfa28b9f1..c0e297d8554d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -759,7 +759,6 @@ $help-shortcut-header-color: #333;
*/
$issues-today-bg: #f3fff2 !default;
$issues-today-border: #e1e8d5 !default;
-$compare-display-color: #888;
/*
* Label
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index d8fc94cce0c5..c4df2e102bd3 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -220,21 +220,12 @@
}
.cross-project-reference {
- color: inherit;
-
span {
- white-space: nowrap;
width: 85%;
- overflow: hidden;
- position: relative;
- display: inline-block;
- text-overflow: ellipsis;
}
button {
- float: right;
padding: 1px 5px;
- background-color: $gray-light;
}
}
@@ -563,35 +554,17 @@
}
}
-.participants-list {
- display: flex;
- flex-wrap: wrap;
-}
-
-.user-list {
- display: flex;
- flex-wrap: wrap;
-}
-
.participants-author {
- display: inline-block;
- padding: 0 $gl-padding-8 $gl-padding-8 0;
-
&:nth-of-type(7n) {
padding-right: 0;
}
- .author-link {
- display: block;
- }
-
.avatar.avatar-inline {
margin: 0;
}
}
.user-item {
- display: inline-block;
padding: 5px;
flex-basis: 20%;
@@ -803,10 +776,6 @@
}
}
-.add-issuable-form-actions {
- margin-top: $gl-padding;
-}
-
.time-tracker {
.sidebar-collapsed-icon {
> .stopwatch-svg {
@@ -839,18 +808,7 @@
}
.compare-display-container {
- display: flex;
- justify-content: space-between;
- margin-top: 5px;
-
- .compare-display {
- font-size: 13px;
- color: $compare-display-color;
-
- .compare-value {
- color: $gl-text-color;
- }
- }
+ font-size: 13px;
}
.time-tracking-help-state {
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index 56b2b0d5801e..8ed40f74c885 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -164,8 +164,8 @@
.sidebar-collapsed-icon.dont-change-state
= clipboard_button(text: milestone_ref, title: s_('MilestoneSidebar|Copy reference'), placement: "left", boundary: 'viewport')
.cross-project-reference.hide-collapsed
- %span
+ %span.gl-display-inline-block.gl-text-truncate
= s_('MilestoneSidebar|Reference:')
%span{ title: milestone_ref }
= milestone_ref
- = clipboard_button(text: milestone_ref, title: s_('MilestoneSidebar|Copy reference'), placement: "left", boundary: 'viewport')
+ = clipboard_button(text: milestone_ref, title: s_('MilestoneSidebar|Copy reference'), placement: "left", boundary: 'viewport', class: 'btn-clipboard btn-transparent gl-float-right gl-bg-gray-10')
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 0417dbeeede1..2708159979e2 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -783,20 +783,25 @@ often using fixtures to validate correct integration with the backend code.
### Use fixtures
-Jest uses `spec/frontend/__helpers__/fixtures.js` to import fixtures in tests.
-
-The following are examples of tests that work for Jest:
+To import a JSON fixture, `import` it using the `test_fixtures` alias.
```javascript
+import responseBody from 'test_fixtures/some/fixture.json' // loads spec/frontend/fixtures/some/fixture.json
+
it('makes a request', () => {
- const responseBody = getJSONFixture('some/fixture.json'); // loads spec/frontend/fixtures/some/fixture.json
axiosMock.onGet(endpoint).reply(200, responseBody);
myButton.click();
// ...
});
+```
+For other fixtures, Jest uses `spec/frontend/__helpers__/fixtures.js` to import them in tests.
+
+The following are examples of tests that work for Jest:
+
+```javascript
it('uses some HTML element', () => {
loadFixtures('some/page.html'); // loads spec/frontend/fixtures/some/page.html and adds it to the DOM
@@ -860,7 +865,7 @@ end
This will create a new fixture located at
`tmp/tests/frontend/fixtures-ee/graphql/releases/graphql/queries/all_releases.query.graphql.json`.
-You can import the JSON fixture in a Jest test using the `getJSONFixture` method
+You can import the JSON fixture in a Jest test using the `test_fixtures` alias
[as described below](#use-fixtures).
## Data-driven tests
diff --git a/jest.config.base.js b/jest.config.base.js
index e0d5afbdbc97..7cf1136a0732 100644
--- a/jest.config.base.js
+++ b/jest.config.base.js
@@ -112,6 +112,7 @@ module.exports = (path, options = {}) => {
cacheDirectory: '
/tmp/cache/jest',
modulePathIgnorePatterns: ['/.yarn-cache/'],
reporters,
+ resolver: './jest_resolver.js',
setupFilesAfterEnv: [`/${path}/test_setup.js`, 'jest-canvas-mock'],
restoreMocks: true,
transform: {
diff --git a/jest_resolver.js b/jest_resolver.js
new file mode 100644
index 000000000000..6cbc1b9f18fe
--- /dev/null
+++ b/jest_resolver.js
@@ -0,0 +1,17 @@
+const fs = require('fs');
+
+// Wrap jest default resolver to detect missing frontend fixtures.
+module.exports = (request, options) => {
+ try {
+ return options.defaultResolver(request, options);
+ } catch (e) {
+ if (request.match(/tmp\/tests\/frontend\/fixtures/) && !fs.existsSync(request)) {
+ console.error(
+ '\x1b[1m\x1b[41m\x1b[30m %s \x1b[0m %s',
+ '!',
+ `Fixture file ${request} does not exist. Did you run bin/rake frontend:fixtures?`,
+ );
+ }
+ throw e;
+ }
+};
diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml
index 145e6c8961ac..2686a14f9eb3 100644
--- a/spec/frontend/.eslintrc.yml
+++ b/spec/frontend/.eslintrc.yml
@@ -26,4 +26,9 @@ rules:
- off
"@gitlab/no-global-event-off":
- off
-
+ import/no-unresolved:
+ - error
+ # The test fixtures and graphql schema are dynamically generated in CI
+ # during the `frontend-fixtures` and `graphql-schema-dump` jobs.
+ # They may not be present during linting.
+ - ignore: ['^test_fixtures\/', 'tmp/tests/graphql/gitlab_schema.graphql']
diff --git a/spec/frontend/deploy_freeze/helpers.js b/spec/frontend/deploy_freeze/helpers.js
index 598f14d45f64..43e66183ab54 100644
--- a/spec/frontend/deploy_freeze/helpers.js
+++ b/spec/frontend/deploy_freeze/helpers.js
@@ -1,7 +1,8 @@
+import freezePeriodsFixture from 'test_fixtures/api/freeze-periods/freeze_periods.json';
+import timezoneDataFixture from 'test_fixtures/timezones/short.json';
import { secondsToHours } from '~/lib/utils/datetime_utility';
-export const freezePeriodsFixture = getJSONFixture('/api/freeze-periods/freeze_periods.json');
-export const timezoneDataFixture = getJSONFixture('/timezones/short.json');
+export { freezePeriodsFixture, timezoneDataFixture };
export const findTzByName = (identifier = '') =>
timezoneDataFixture.find(({ name }) => name.toLowerCase() === identifier.toLowerCase());
diff --git a/spec/frontend/sidebar/assignees_spec.js b/spec/frontend/sidebar/assignees_spec.js
index be27a800418c..b3a67f18f82c 100644
--- a/spec/frontend/sidebar/assignees_spec.js
+++ b/spec/frontend/sidebar/assignees_spec.js
@@ -3,6 +3,7 @@ import { mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
import UsersMockHelper from 'helpers/user_mock_data_helper';
import Assignee from '~/sidebar/components/assignees/assignees.vue';
+import AssigneeAvatarLink from '~/sidebar/components/assignees/assignee_avatar_link.vue';
import UsersMock from './mock_data';
describe('Assignee component', () => {
@@ -19,6 +20,7 @@ describe('Assignee component', () => {
});
};
+ const findAllAvatarLinks = () => wrapper.findAllComponents(AssigneeAvatarLink);
const findComponentTextNoUsers = () => wrapper.find('[data-testid="no-value"]');
const findCollapsedChildren = () => wrapper.findAll('.sidebar-collapsed-icon > *');
@@ -148,7 +150,7 @@ describe('Assignee component', () => {
editable: true,
});
- expect(wrapper.findAll('.user-item').length).toBe(users.length);
+ expect(findAllAvatarLinks()).toHaveLength(users.length);
expect(wrapper.find('.user-list-more').exists()).toBe(false);
});
@@ -178,9 +180,9 @@ describe('Assignee component', () => {
users,
});
- const userItems = wrapper.findAll('.user-list .user-item a');
+ const userItems = findAllAvatarLinks();
- expect(userItems.length).toBe(3);
+ expect(userItems).toHaveLength(3);
expect(userItems.at(0).attributes('title')).toBe(users[2].name);
});
diff --git a/spec/frontend_integration/test_helpers/mock_server/graphql.js b/spec/frontend_integration/test_helpers/mock_server/graphql.js
index 27396842523e..d4ee7c02839e 100644
--- a/spec/frontend_integration/test_helpers/mock_server/graphql.js
+++ b/spec/frontend_integration/test_helpers/mock_server/graphql.js
@@ -1,9 +1,7 @@
import { buildSchema, graphql } from 'graphql';
import { memoize } from 'lodash';
-// The graphql schema is dynamically generated in CI
-// during the `graphql-schema-dump` job.
-// eslint-disable-next-line global-require, import/no-unresolved
+// eslint-disable-next-line global-require
const getGraphqlSchema = () => require('../../../../tmp/tests/graphql/gitlab_schema.graphql');
const graphqlResolvers = {