Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0c4b9cacd5
commit
51c20446a0
|
|
@ -83,7 +83,6 @@
|
|||
- "{,ee/}fixtures/**/*"
|
||||
- "{,ee/}rubocop/**/*"
|
||||
- "{,ee/}spec/**/*"
|
||||
- "doc/README.md" # Some RSpec test rely on this file
|
||||
|
||||
.code-patterns: &code-patterns
|
||||
- "{package.json,yarn.lock}"
|
||||
|
|
@ -126,7 +125,6 @@
|
|||
- "{,ee/}fixtures/**/*"
|
||||
- "{,ee/}rubocop/**/*"
|
||||
- "{,ee/}spec/**/*"
|
||||
- "doc/README.md" # Some RSpec test rely on this file
|
||||
|
||||
.code-qa-patterns: &code-qa-patterns
|
||||
- "{package.json,yarn.lock}"
|
||||
|
|
@ -168,7 +166,6 @@
|
|||
- "{,ee/}fixtures/**/*"
|
||||
- "{,ee/}rubocop/**/*"
|
||||
- "{,ee/}spec/**/*"
|
||||
- "doc/README.md" # Some RSpec test rely on this file
|
||||
# QA changes
|
||||
- ".dockerignore"
|
||||
- "qa/**/*"
|
||||
|
|
|
|||
3
Gemfile
3
Gemfile
|
|
@ -495,3 +495,6 @@ gem 'mail', '= 2.7.1'
|
|||
|
||||
# File encryption
|
||||
gem 'lockbox', '~> 0.3.3'
|
||||
|
||||
# Email validation
|
||||
gem 'valid_email', '~> 0.1'
|
||||
|
|
|
|||
|
|
@ -842,7 +842,7 @@ GEM
|
|||
rake (>= 0.8.7)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
raindrops (0.19.0)
|
||||
raindrops (0.19.1)
|
||||
rake (12.3.3)
|
||||
rb-fsevent (0.10.2)
|
||||
rb-inotify (0.9.10)
|
||||
|
|
@ -1109,6 +1109,9 @@ GEM
|
|||
equalizer (~> 0.0.9)
|
||||
parser (>= 2.6.5)
|
||||
procto (~> 0.0.2)
|
||||
valid_email (0.1.3)
|
||||
activemodel
|
||||
mail (>= 2.6.1)
|
||||
validate_email (0.1.6)
|
||||
activemodel (>= 3.0)
|
||||
mail (>= 2.2.5)
|
||||
|
|
@ -1402,6 +1405,7 @@ DEPENDENCIES
|
|||
unicorn (~> 5.5)
|
||||
unicorn-worker-killer (~> 0.4.4)
|
||||
unleash (~> 0.1.5)
|
||||
valid_email (~> 0.1)
|
||||
validates_hostname (~> 1.0.6)
|
||||
version_sorter (~> 2.2.4)
|
||||
vmstat (~> 2.3.0)
|
||||
|
|
|
|||
|
|
@ -1,22 +1,15 @@
|
|||
/* global ace */
|
||||
import Editor from '~/editor/editor_lite';
|
||||
|
||||
export function initEditorLite({ el, blobPath, blobContent }) {
|
||||
if (!el) {
|
||||
throw new Error(`"el" parameter is required to initialize Editor`);
|
||||
}
|
||||
let editor;
|
||||
|
||||
if (window?.gon?.features?.monacoSnippets) {
|
||||
editor = new Editor();
|
||||
editor.createInstance({
|
||||
el,
|
||||
blobPath,
|
||||
blobContent,
|
||||
});
|
||||
} else {
|
||||
editor = ace.edit(el);
|
||||
}
|
||||
const editor = new Editor();
|
||||
editor.createInstance({
|
||||
el,
|
||||
blobPath,
|
||||
blobContent,
|
||||
});
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ export default {
|
|||
},
|
||||
|
||||
isDropdownOpen() {
|
||||
return this.$el.classList.contains('open');
|
||||
return this.$el.classList.contains('show');
|
||||
},
|
||||
|
||||
pipelineActionRequestComplete() {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
GlProgressBar,
|
||||
GlLink,
|
||||
GlBadge,
|
||||
GlDeprecatedButton,
|
||||
GlButton,
|
||||
GlTooltipDirective,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
|
|
@ -17,7 +17,7 @@ export default {
|
|||
GlProgressBar,
|
||||
GlLink,
|
||||
GlBadge,
|
||||
GlDeprecatedButton,
|
||||
GlButton,
|
||||
GlSprintf,
|
||||
},
|
||||
directives: {
|
||||
|
|
@ -134,13 +134,9 @@ export default {
|
|||
<span :key="'bullet-' + milestone.id" class="append-right-4">•</span>
|
||||
</template>
|
||||
<template v-if="shouldRenderShowMoreLink(index)">
|
||||
<gl-deprecated-button
|
||||
:key="'more-button-' + milestone.id"
|
||||
variant="link"
|
||||
@click="toggleShowAll"
|
||||
>
|
||||
<gl-button :key="'more-button-' + milestone.id" variant="link" @click="toggleShowAll">
|
||||
{{ moreText }}
|
||||
</gl-deprecated-button>
|
||||
</gl-button>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,18 +3,6 @@ import setupCollapsibleInputs from './collapsible_input';
|
|||
|
||||
let editor;
|
||||
|
||||
const initAce = () => {
|
||||
const editorEl = document.getElementById('editor');
|
||||
const form = document.querySelector('.snippet-form-holder form');
|
||||
const content = document.querySelector('.snippet-file-content');
|
||||
|
||||
editor = initEditorLite({ el: editorEl });
|
||||
|
||||
form.addEventListener('submit', () => {
|
||||
content.value = editor.getValue();
|
||||
});
|
||||
};
|
||||
|
||||
const initMonaco = () => {
|
||||
const editorEl = document.getElementById('editor');
|
||||
const contentEl = document.querySelector('.snippet-file-content');
|
||||
|
|
@ -36,15 +24,7 @@ const initMonaco = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const initEditor = () => {
|
||||
if (window?.gon?.features?.monacoSnippets) {
|
||||
initMonaco();
|
||||
} else {
|
||||
initAce();
|
||||
}
|
||||
export default () => {
|
||||
initMonaco();
|
||||
setupCollapsibleInputs();
|
||||
};
|
||||
|
||||
export default () => {
|
||||
initEditor();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ class Email < ApplicationRecord
|
|||
|
||||
belongs_to :user, optional: false
|
||||
|
||||
validates :email, presence: true, uniqueness: true, devise_email: true
|
||||
validates :email, presence: true, uniqueness: true
|
||||
validate :validate_email_format
|
||||
validate :unique_email, if: ->(email) { email.email_changed? }
|
||||
|
||||
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
||||
|
|
@ -30,6 +31,10 @@ class Email < ApplicationRecord
|
|||
user.accept_pending_invitations!
|
||||
end
|
||||
|
||||
def validate_email_format
|
||||
self.errors.add(:email, I18n.t(:invalid, scope: 'valid_email.validations.email')) unless ValidateEmail.valid?(self.email)
|
||||
end
|
||||
|
||||
# once email is confirmed, update the gpg signatures
|
||||
def update_invalid_gpg_signatures
|
||||
user.update_invalid_gpg_signatures if confirmed?
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
- if Feature.disabled?(:monaco_snippets)
|
||||
- content_for :page_specific_javascripts do
|
||||
= page_specific_javascript_tag('lib/ace.js')
|
||||
|
||||
- if Feature.enabled?(:snippets_edit_vue)
|
||||
#js-snippet-edit.snippet-form{ data: {'project_path': @snippet.project&.full_path, 'snippet-gid': @snippet.new_record? ? '' : @snippet.to_global_id, 'markdown-preview-path': preview_markdown_path(parent), 'markdown-docs-path': help_page_path('user/markdown'), 'visibility-help-link': help_page_path("public_access/public_access") } }
|
||||
- else
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Updated deprecated buttons in release page
|
||||
merge_request: 30941
|
||||
author: Özgür Adem Işıklı @iozguradem
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Change validation rules for profile email addresses
|
||||
merge_request: 30633
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Enable Monaco for editing Snippets by default
|
||||
merge_request: 30892
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Project Vulnerabilities API **(ULTIMATE)**
|
||||
# Vulnerability export API **(ULTIMATE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/197494) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/197494) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10. [Updated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30397) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.0.
|
||||
|
||||
CAUTION: **Caution:**
|
||||
This API is currently in development and is protected by a **disabled**
|
||||
|
|
@ -17,21 +17,21 @@ across GitLab releases.
|
|||
|
||||
Every API call to vulnerability exports must be [authenticated](README.md#authentication).
|
||||
|
||||
## Create a project-level vulnerability export
|
||||
|
||||
Creates a new vulnerability export for a project.
|
||||
|
||||
Vulnerability export permissions inherit permissions from their project. If a project is
|
||||
private and a user isn't a member of the project to which the vulnerability
|
||||
belongs, requests to that project return a `404 Not Found` status code.
|
||||
Vulnerability exports can be only accessed by the export's author.
|
||||
|
||||
## Create vulnerability export
|
||||
|
||||
Creates a new vulnerability export.
|
||||
|
||||
If an authenticated user doesn't have permission to
|
||||
[create a new vulnerability](../user/permissions.md#project-members-permissions),
|
||||
this request results in a `403` status code.
|
||||
|
||||
```plaintext
|
||||
POST /projects/:id/vulnerability_exports
|
||||
POST /security/projects/:id/vulnerability_exports
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|
|
@ -39,7 +39,7 @@ POST /projects/:id/vulnerability_exports
|
|||
| `id` | integer or string | yes | The ID or [URL-encoded path](README.md#namespaced-path-encoding) of the project which the authenticated user is a member of |
|
||||
|
||||
```shell
|
||||
curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/vulnerability_exports
|
||||
curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/security/projects/1/vulnerability_exports
|
||||
```
|
||||
|
||||
The created vulnerability export will be automatically deleted after 1 hour.
|
||||
|
|
@ -56,8 +56,40 @@ Example response:
|
|||
"started_at": null,
|
||||
"finished_at": null,
|
||||
"_links": {
|
||||
"self": "https://gitlab.example.com/api/v4/projects/1/vulnerability_exports/2",
|
||||
"download": "https://gitlab.example.com/api/v4/projects/1/vulnerability_exports/2/download"
|
||||
"self": "https://gitlab.example.com/api/v4/security/vulnerability_exports/2",
|
||||
"download": "https://gitlab.example.com/api/v4/security/vulnerability_exports/2/download"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Create an instance-level vulnerability export
|
||||
|
||||
Creates a new vulnerability export for the projects of the user selected in the Security Dashboard.
|
||||
|
||||
```plaintext
|
||||
POST /security/vulnerability_exports
|
||||
```
|
||||
|
||||
```shell
|
||||
curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/security/vulnerability_exports
|
||||
```
|
||||
|
||||
The created vulnerability export is automatically deleted after one hour.
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 2,
|
||||
"created_at": "2020-03-30T09:35:38.746Z",
|
||||
"project_id": null,
|
||||
"format": "csv",
|
||||
"status": "created",
|
||||
"started_at": null,
|
||||
"finished_at": null,
|
||||
"_links": {
|
||||
"self": "https://gitlab.example.com/api/v4/security/vulnerability_exports/2",
|
||||
"download": "https://gitlab.example.com/api/v4/security/vulnerability_exports/2/download"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -67,16 +99,15 @@ Example response:
|
|||
Gets a single vulnerability export.
|
||||
|
||||
```plaintext
|
||||
POST /projects/:id/vulnerability_exports/:vulnerability_export_id
|
||||
GET /security/vulnerability_exports/:id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer or string | yes | The vulnerability's ID |
|
||||
| `vulnerability_export_id` | integer or string | yes | The vulnerability export's ID |
|
||||
| `id` | integer or string | yes | The vulnerability export's ID |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/vulnerability_exports/2
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/security/vulnerability_exports/2
|
||||
```
|
||||
|
||||
If the vulnerability export isn't finished, the response is `202 Accepted`.
|
||||
|
|
@ -93,8 +124,8 @@ Example response:
|
|||
"started_at": "2020-03-30T09:36:54.469Z",
|
||||
"finished_at": "2020-03-30T09:36:55.008Z",
|
||||
"_links": {
|
||||
"self": "https://gitlab.example.com/api/v4/projects/1/vulnerability_exports/2",
|
||||
"download": "https://gitlab.example.com/api/v4/projects/1/vulnerability_exports/2/download"
|
||||
"self": "https://gitlab.example.com/api/v4/security/vulnerability_exports/2",
|
||||
"download": "https://gitlab.example.com/api/v4/security/vulnerability_exports/2/download"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -104,16 +135,15 @@ Example response:
|
|||
Downloads a single vulnerability export.
|
||||
|
||||
```plaintext
|
||||
POST /projects/:id/vulnerability_exports/:vulnerability_export_id/download
|
||||
GET /security/vulnerability_exports/:id/download
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer or string | yes | The vulnerability's ID |
|
||||
| `vulnerability_export_id` | integer or string | yes | The vulnerability export's ID |
|
||||
| `id` | integer or string | yes | The vulnerability export's ID |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/vulnerability_exports/2/download
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/security/vulnerability_exports/2/download
|
||||
```
|
||||
|
||||
The response will be `404 Not Found` if the vulnerability export is not finished yet or was not found.
|
||||
|
|
|
|||
|
|
@ -405,8 +405,6 @@ merge request with new or changed docs is submitted, are:
|
|||
- [`internal_anchors`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab/ci/docs.gitlab-ci.yml#L69)
|
||||
checks that all internal anchors (ex: `[link](../index.md#internal_anchor)`)
|
||||
are valid.
|
||||
- If any code or the `doc/README.md` file is changed, a full pipeline will run, which
|
||||
runs tests for [`/help`](#gitlab-help-tests).
|
||||
|
||||
### Running tests
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ The Packages feature allows GitLab to act as a repository for the following:
|
|||
|
||||
If you cannot find the **{package}** **Packages > List** entry under your
|
||||
project's sidebar, it is not enabled in your GitLab instance. Ask your
|
||||
administrator to enable GitLab Package Registry following the administration
|
||||
documentation.
|
||||
administrator to enable GitLab Package Registry following the [administration
|
||||
documentation](../../administration/packages/index.md).
|
||||
|
||||
Once enabled for your GitLab instance, to enable Package Registry for your
|
||||
project:
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ The following table depicts the various user permission levels in a project.
|
|||
| Manage [push rules](../push_rules/push_rules.md) | | | | ✓ | ✓ |
|
||||
| Switch visibility level | | | | | ✓ |
|
||||
| Transfer project to another namespace | | | | | ✓ |
|
||||
| Remove fork relationship | | | | | ✓ |
|
||||
| Remove project | | | | | ✓ |
|
||||
| Delete issues | | | | | ✓ |
|
||||
| Disable notification emails | | | | | ✓ |
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ module Gitlab
|
|||
# Initialize gon.features with any flags that should be
|
||||
# made globally available to the frontend
|
||||
push_frontend_feature_flag(:snippets_vue, default_enabled: false)
|
||||
push_frontend_feature_flag(:monaco_snippets, default_enabled: false)
|
||||
push_frontend_feature_flag(:monaco_blobs, default_enabled: false)
|
||||
push_frontend_feature_flag(:monaco_ci, default_enabled: false)
|
||||
push_frontend_feature_flag(:snippets_edit_vue, default_enabled: false)
|
||||
|
|
|
|||
|
|
@ -10,12 +10,20 @@ describe Profiles::EmailsController do
|
|||
end
|
||||
|
||||
describe '#create' do
|
||||
let(:email_params) { { email: "add_email@example.com" } }
|
||||
context 'when email address is valid' do
|
||||
let(:email_params) { { email: "add_email@example.com" } }
|
||||
|
||||
it 'sends an email confirmation' do
|
||||
expect { post(:create, params: { email: email_params }) }.to change { ActionMailer::Base.deliveries.size }
|
||||
expect(ActionMailer::Base.deliveries.last.to).to eq [email_params[:email]]
|
||||
expect(ActionMailer::Base.deliveries.last.subject).to match "Confirmation instructions"
|
||||
it 'sends an email confirmation' do
|
||||
expect { post(:create, params: { email: email_params }) }.to change { ActionMailer::Base.deliveries.size }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when email address is invalid' do
|
||||
let(:email_params) { { email: "test.@example.com" } }
|
||||
|
||||
it 'does not send an email confirmation' do
|
||||
expect { post(:create, params: { email: email_params }) }.not_to change { ActionMailer::Base.deliveries.size }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,35 +4,9 @@ require 'spec_helper'
|
|||
|
||||
describe 'Help Pages' do
|
||||
describe 'Get the main help page' do
|
||||
shared_examples_for 'help page' do |prefix: ''|
|
||||
it 'prefixes links correctly' do
|
||||
expect(page).to have_selector(%(div.documentation-index > table tbody tr td a[href="#{prefix}/help/api/README.md"]))
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a trailing slash' do
|
||||
before do
|
||||
visit help_path
|
||||
end
|
||||
|
||||
it_behaves_like 'help page'
|
||||
end
|
||||
|
||||
context 'with a trailing slash' do
|
||||
before do
|
||||
visit help_path + '/'
|
||||
end
|
||||
|
||||
it_behaves_like 'help page'
|
||||
end
|
||||
|
||||
context 'with a relative installation' do
|
||||
before do
|
||||
stub_config_setting(relative_url_root: '/gitlab')
|
||||
visit help_path
|
||||
end
|
||||
|
||||
it_behaves_like 'help page', prefix: '/gitlab'
|
||||
before do
|
||||
allow(File).to receive(:read).and_call_original
|
||||
allow(File).to receive(:read).with(Rails.root.join('doc', 'README.md')).and_return(fixture_file('sample_doc.md'))
|
||||
end
|
||||
|
||||
context 'quick link shortcuts', :js do
|
||||
|
|
|
|||
|
|
@ -31,6 +31,15 @@ describe 'Profile > Emails' do
|
|||
expect(email).to be_nil
|
||||
expect(page).to have_content('Email has already been taken')
|
||||
end
|
||||
|
||||
it 'does not add an invalid email' do
|
||||
fill_in('Email', with: 'test.@example.com')
|
||||
click_button('Add email address')
|
||||
|
||||
email = user.emails.find_by(email: email)
|
||||
expect(email).to be_nil
|
||||
expect(page).to have_content('Email is invalid')
|
||||
end
|
||||
end
|
||||
|
||||
it 'User removes email' do
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ require 'spec_helper'
|
|||
shared_examples_for 'snippet editor' do
|
||||
before do
|
||||
stub_feature_flags(snippets_edit_vue: false)
|
||||
stub_feature_flags(monaco_snippets: flag)
|
||||
end
|
||||
|
||||
def description_field
|
||||
|
|
@ -20,7 +19,7 @@ shared_examples_for 'snippet editor' do
|
|||
fill_in 'project_snippet_description', with: 'My Snippet **Description**'
|
||||
|
||||
page.within('.file-editor') do
|
||||
el = flag == true ? find('.inputarea') : find('.ace_text-input', visible: false)
|
||||
el = find('.inputarea')
|
||||
el.send_keys 'Hello World!'
|
||||
end
|
||||
end
|
||||
|
|
@ -145,15 +144,5 @@ describe 'Projects > Snippets > Create Snippet', :js do
|
|||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
|
||||
context 'when using Monaco' do
|
||||
it_behaves_like "snippet editor" do
|
||||
let(:flag) { true }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using ACE' do
|
||||
it_behaves_like "snippet editor" do
|
||||
let(:flag) { false }
|
||||
end
|
||||
end
|
||||
it_behaves_like "snippet editor"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ shared_examples_for 'snippet editor' do
|
|||
stub_feature_flags(allow_possible_spam: false)
|
||||
stub_feature_flags(snippets_vue: false)
|
||||
stub_feature_flags(snippets_edit_vue: false)
|
||||
stub_feature_flags(monaco_snippets: flag)
|
||||
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
|
||||
|
||||
Gitlab::CurrentSettings.update!(
|
||||
|
|
@ -35,7 +34,7 @@ shared_examples_for 'snippet editor' do
|
|||
|
||||
find('#personal_snippet_visibility_level_20').set(true)
|
||||
page.within('.file-editor') do
|
||||
el = flag == true ? find('.inputarea') : find('.ace_text-input', visible: false)
|
||||
el = find('.inputarea')
|
||||
el.send_keys 'Hello World!'
|
||||
end
|
||||
end
|
||||
|
|
@ -126,15 +125,5 @@ end
|
|||
describe 'User creates snippet', :js do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
context 'when using Monaco' do
|
||||
it_behaves_like "snippet editor" do
|
||||
let(:flag) { true }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using ACE' do
|
||||
it_behaves_like "snippet editor" do
|
||||
let(:flag) { false }
|
||||
end
|
||||
end
|
||||
it_behaves_like "snippet editor"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ shared_examples_for 'snippet editor' do
|
|||
before do
|
||||
stub_feature_flags(snippets_vue: false)
|
||||
stub_feature_flags(snippets_edit_vue: false)
|
||||
stub_feature_flags(monaco_snippets: flag)
|
||||
sign_in(user)
|
||||
visit new_snippet_path
|
||||
end
|
||||
|
|
@ -23,7 +22,7 @@ shared_examples_for 'snippet editor' do
|
|||
fill_in 'personal_snippet_description', with: 'My Snippet **Description**'
|
||||
|
||||
page.within('.file-editor') do
|
||||
el = flag == true ? find('.inputarea') : find('.ace_text-input', visible: false)
|
||||
el = find('.inputarea')
|
||||
el.send_keys 'Hello World!'
|
||||
end
|
||||
end
|
||||
|
|
@ -136,7 +135,7 @@ shared_examples_for 'snippet editor' do
|
|||
fill_in 'personal_snippet_title', with: 'My Snippet Title'
|
||||
page.within('.file-editor') do
|
||||
find(:xpath, "//input[@id='personal_snippet_file_name']").set 'snippet+file+name'
|
||||
el = flag == true ? find('.inputarea') : find('.ace_text-input', visible: false)
|
||||
el = find('.inputarea')
|
||||
el.send_keys 'Hello World!'
|
||||
end
|
||||
|
||||
|
|
@ -154,15 +153,5 @@ describe 'User creates snippet', :js do
|
|||
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
context 'when using Monaco' do
|
||||
it_behaves_like "snippet editor" do
|
||||
let(:flag) { true }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using ACE' do
|
||||
it_behaves_like "snippet editor" do
|
||||
let(:flag) { false }
|
||||
end
|
||||
end
|
||||
it_behaves_like "snippet editor"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
[GitLab API](api/README.md)
|
||||
|
|
@ -8,11 +8,6 @@ jest.mock('~/editor/editor_lite', () => {
|
|||
});
|
||||
});
|
||||
|
||||
const mockCreateAceInstance = jest.fn();
|
||||
global.ace = {
|
||||
edit: mockCreateAceInstance,
|
||||
};
|
||||
|
||||
describe('Blob utilities', () => {
|
||||
beforeEach(() => {
|
||||
Editor.mockClear();
|
||||
|
|
@ -29,21 +24,6 @@ describe('Blob utilities', () => {
|
|||
});
|
||||
|
||||
describe('Monaco editor', () => {
|
||||
let origProp;
|
||||
|
||||
beforeEach(() => {
|
||||
origProp = window.gon;
|
||||
window.gon = {
|
||||
features: {
|
||||
monacoSnippets: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.gon = origProp;
|
||||
});
|
||||
|
||||
it('initializes the Editor Lite', () => {
|
||||
utils.initEditorLite({ el: editorEl });
|
||||
expect(Editor).toHaveBeenCalled();
|
||||
|
|
@ -69,27 +49,5 @@ describe('Blob utilities', () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
describe('ACE editor', () => {
|
||||
let origProp;
|
||||
|
||||
beforeEach(() => {
|
||||
origProp = window.gon;
|
||||
window.gon = {
|
||||
features: {
|
||||
monacoSnippets: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.gon = origProp;
|
||||
});
|
||||
|
||||
it('does not initialize the Editor Lite', () => {
|
||||
utils.initEditorLite({ el: editorEl });
|
||||
expect(Editor).not.toHaveBeenCalled();
|
||||
expect(mockCreateAceInstance).toHaveBeenCalledWith(editorEl);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import PipelineArtifacts from '~/pipelines/components/pipelines_artifacts.vue';
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
|
||||
describe('Pipelines Artifacts dropdown', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(PipelineArtifacts, {
|
||||
propsData: {
|
||||
artifacts: [
|
||||
{
|
||||
name: 'artifact',
|
||||
path: '/download/path',
|
||||
},
|
||||
{
|
||||
name: 'artifact two',
|
||||
path: '/download/path-two',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findGlLink = () => wrapper.find(GlLink);
|
||||
const findAllGlLinks = () => wrapper.find('.dropdown-menu').findAll(GlLink);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
it('should render a dropdown with all the provided artifacts', () => {
|
||||
expect(findAllGlLinks()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should render a link with the provided path', () => {
|
||||
expect(findGlLink().attributes('href')).toEqual('/download/path');
|
||||
|
||||
expect(findGlLink().text()).toContain('artifact');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import StageComponent from '~/pipelines/components/stage.vue';
|
||||
import eventHub from '~/pipelines/event_hub';
|
||||
import { stageReply } from './mock_data';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
||||
describe('Pipelines stage component', () => {
|
||||
let wrapper;
|
||||
let mock;
|
||||
|
||||
const defaultProps = {
|
||||
stage: {
|
||||
status: {
|
||||
group: 'success',
|
||||
icon: 'status_success',
|
||||
title: 'success',
|
||||
},
|
||||
dropdown_path: 'path.json',
|
||||
},
|
||||
updateDropdown: false,
|
||||
};
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = mount(StageComponent, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('default', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('should render a dropdown with the status icon', () => {
|
||||
expect(wrapper.attributes('class')).toEqual('dropdown');
|
||||
expect(wrapper.find('svg').exists()).toBe(true);
|
||||
expect(wrapper.find('button').attributes('data-toggle')).toEqual('dropdown');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with successful request', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet('path.json').reply(200, stageReply);
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('should render the received data and emit `clickedDropdown` event', () => {
|
||||
jest.spyOn(eventHub, '$emit');
|
||||
wrapper.find('button').trigger('click');
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.find('.js-builds-dropdown-container ul').text()).toContain(
|
||||
stageReply.latest_statuses[0].name,
|
||||
);
|
||||
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when request fails', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet('path.json').reply(500);
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('should close the dropdown', () => {
|
||||
wrapper.setMethods({
|
||||
closeDropdown: jest.fn(),
|
||||
isDropdownOpen: jest.fn().mockReturnValue(false),
|
||||
});
|
||||
|
||||
wrapper.find('button').trigger('click');
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.closeDropdown).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('update endpoint correctly', () => {
|
||||
beforeEach(() => {
|
||||
const copyStage = Object.assign({}, stageReply);
|
||||
copyStage.latest_statuses[0].name = 'this is the updated content';
|
||||
mock.onGet('bar.json').reply(200, copyStage);
|
||||
createComponent({
|
||||
stage: {
|
||||
status: {
|
||||
group: 'running',
|
||||
icon: 'status_running',
|
||||
title: 'running',
|
||||
},
|
||||
dropdown_path: 'bar.json',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should update the stage to request the new endpoint provided', () => {
|
||||
return wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
wrapper.find('button').trigger('click');
|
||||
return waitForPromises();
|
||||
})
|
||||
.then(() => {
|
||||
expect(wrapper.find('.js-builds-dropdown-container ul').text()).toContain(
|
||||
'this is the updated content',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('pipelineActionRequestComplete', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet('path.json').reply(200, stageReply);
|
||||
|
||||
mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
|
||||
|
||||
createComponent({ type: 'PIPELINES_TABLE' });
|
||||
});
|
||||
|
||||
describe('within pipeline table', () => {
|
||||
it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', () => {
|
||||
jest.spyOn(eventHub, '$emit');
|
||||
|
||||
wrapper.find('button').trigger('click');
|
||||
|
||||
return waitForPromises()
|
||||
.then(() => {
|
||||
wrapper.find('.js-ci-action').trigger('click');
|
||||
|
||||
return waitForPromises();
|
||||
})
|
||||
.then(() => {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { GlProgressBar, GlLink, GlBadge, GlDeprecatedButton } from '@gitlab/ui';
|
||||
import { GlProgressBar, GlLink, GlBadge, GlButton } from '@gitlab/ui';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import ReleaseBlockMilestoneInfo from '~/releases/components/release_block_milestone_info.vue';
|
||||
import { milestones as originalMilestones } from '../mock_data';
|
||||
|
|
@ -106,7 +106,7 @@ describe('Release block milestone info', () => {
|
|||
|
||||
const clickShowMoreFewerButton = () => {
|
||||
milestoneListContainer()
|
||||
.find(GlDeprecatedButton)
|
||||
.find(GlButton)
|
||||
.trigger('click');
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
|
|
|
|||
|
|
@ -1,94 +1,85 @@
|
|||
import Editor from '~/editor/editor_lite';
|
||||
import { initEditor } from '~/snippet/snippet_bundle';
|
||||
import initEditor from '~/snippet/snippet_bundle';
|
||||
import { setHTMLFixture } from 'helpers/fixtures';
|
||||
|
||||
jest.mock('~/editor/editor_lite', () => jest.fn());
|
||||
|
||||
describe('Snippet editor', () => {
|
||||
describe('Monaco editor for Snippets', () => {
|
||||
let oldGon;
|
||||
let editorEl;
|
||||
let contentEl;
|
||||
let fileNameEl;
|
||||
let form;
|
||||
let editorEl;
|
||||
let contentEl;
|
||||
let fileNameEl;
|
||||
let form;
|
||||
|
||||
const mockName = 'foo.bar';
|
||||
const mockContent = 'Foo Bar';
|
||||
const updatedMockContent = 'New Foo Bar';
|
||||
const mockName = 'foo.bar';
|
||||
const mockContent = 'Foo Bar';
|
||||
const updatedMockContent = 'New Foo Bar';
|
||||
|
||||
const mockEditor = {
|
||||
createInstance: jest.fn(),
|
||||
updateModelLanguage: jest.fn(),
|
||||
getValue: jest.fn().mockReturnValueOnce(updatedMockContent),
|
||||
};
|
||||
Editor.mockImplementation(() => mockEditor);
|
||||
const mockEditor = {
|
||||
createInstance: jest.fn(),
|
||||
updateModelLanguage: jest.fn(),
|
||||
getValue: jest.fn().mockReturnValueOnce(updatedMockContent),
|
||||
};
|
||||
Editor.mockImplementation(() => mockEditor);
|
||||
|
||||
function setUpFixture(name, content) {
|
||||
setHTMLFixture(`
|
||||
<div class="snippet-form-holder">
|
||||
<form>
|
||||
<input class="js-snippet-file-name" type="text" value="${name}">
|
||||
<input class="snippet-file-content" type="hidden" value="${content}">
|
||||
<pre id="editor"></pre>
|
||||
</form>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
function setUpFixture(name, content) {
|
||||
setHTMLFixture(`
|
||||
<div class="snippet-form-holder">
|
||||
<form>
|
||||
<input class="js-snippet-file-name" type="text" value="${name}">
|
||||
<input class="snippet-file-content" type="hidden" value="${content}">
|
||||
<pre id="editor"></pre>
|
||||
</form>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
function bootstrap(name = '', content = '') {
|
||||
setUpFixture(name, content);
|
||||
editorEl = document.getElementById('editor');
|
||||
contentEl = document.querySelector('.snippet-file-content');
|
||||
fileNameEl = document.querySelector('.js-snippet-file-name');
|
||||
form = document.querySelector('.snippet-form-holder form');
|
||||
function bootstrap(name = '', content = '') {
|
||||
setUpFixture(name, content);
|
||||
editorEl = document.getElementById('editor');
|
||||
contentEl = document.querySelector('.snippet-file-content');
|
||||
fileNameEl = document.querySelector('.js-snippet-file-name');
|
||||
form = document.querySelector('.snippet-form-holder form');
|
||||
|
||||
initEditor();
|
||||
}
|
||||
initEditor();
|
||||
}
|
||||
|
||||
function createEvent(name) {
|
||||
return new Event(name, {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
oldGon = window.gon;
|
||||
window.gon = { features: { monacoSnippets: true } };
|
||||
bootstrap(mockName, mockContent);
|
||||
function createEvent(name) {
|
||||
return new Event(name, {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
window.gon = oldGon;
|
||||
});
|
||||
beforeEach(() => {
|
||||
bootstrap(mockName, mockContent);
|
||||
});
|
||||
|
||||
it('correctly initializes Editor', () => {
|
||||
expect(mockEditor.createInstance).toHaveBeenCalledWith({
|
||||
el: editorEl,
|
||||
blobPath: mockName,
|
||||
blobContent: mockContent,
|
||||
});
|
||||
});
|
||||
|
||||
it('listens to file name changes and updates syntax highlighting of code', () => {
|
||||
expect(mockEditor.updateModelLanguage).not.toHaveBeenCalled();
|
||||
|
||||
const event = createEvent('change');
|
||||
|
||||
fileNameEl.value = updatedMockContent;
|
||||
fileNameEl.dispatchEvent(event);
|
||||
|
||||
expect(mockEditor.updateModelLanguage).toHaveBeenCalledWith(updatedMockContent);
|
||||
});
|
||||
|
||||
it('listens to form submit event and populates the hidden field with most recent version of the content', () => {
|
||||
expect(contentEl.value).toBe(mockContent);
|
||||
|
||||
const event = createEvent('submit');
|
||||
|
||||
form.dispatchEvent(event);
|
||||
expect(contentEl.value).toBe(updatedMockContent);
|
||||
it('correctly initializes Editor', () => {
|
||||
expect(mockEditor.createInstance).toHaveBeenCalledWith({
|
||||
el: editorEl,
|
||||
blobPath: mockName,
|
||||
blobContent: mockContent,
|
||||
});
|
||||
});
|
||||
|
||||
it('listens to file name changes and updates syntax highlighting of code', () => {
|
||||
expect(mockEditor.updateModelLanguage).not.toHaveBeenCalled();
|
||||
|
||||
const event = createEvent('change');
|
||||
|
||||
fileNameEl.value = updatedMockContent;
|
||||
fileNameEl.dispatchEvent(event);
|
||||
|
||||
expect(mockEditor.updateModelLanguage).toHaveBeenCalledWith(updatedMockContent);
|
||||
});
|
||||
|
||||
it('listens to form submit event and populates the hidden field with most recent version of the content', () => {
|
||||
expect(contentEl.value).toBe(mockContent);
|
||||
|
||||
const event = createEvent('submit');
|
||||
|
||||
form.dispatchEvent(event);
|
||||
expect(contentEl.value).toBe(updatedMockContent);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import artifactsComp from '~/pipelines/components/pipelines_artifacts.vue';
|
||||
|
||||
describe('Pipelines Artifacts dropdown', () => {
|
||||
let component;
|
||||
let artifacts;
|
||||
|
||||
beforeEach(() => {
|
||||
const ArtifactsComponent = Vue.extend(artifactsComp);
|
||||
|
||||
artifacts = [
|
||||
{
|
||||
name: 'artifact',
|
||||
path: '/download/path',
|
||||
},
|
||||
];
|
||||
|
||||
component = new ArtifactsComponent({
|
||||
propsData: {
|
||||
artifacts,
|
||||
},
|
||||
}).$mount();
|
||||
});
|
||||
|
||||
it('should render a dropdown with the provided artifacts', () => {
|
||||
expect(component.$el.querySelectorAll('.dropdown-menu li').length).toEqual(artifacts.length);
|
||||
});
|
||||
|
||||
it('should render a link with the provided path', () => {
|
||||
expect(component.$el.querySelector('.dropdown-menu li a').getAttribute('href')).toEqual(
|
||||
artifacts[0].path,
|
||||
);
|
||||
|
||||
expect(component.$el.querySelector('.dropdown-menu li a').textContent).toContain(
|
||||
artifacts[0].name,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import stage from '~/pipelines/components/stage.vue';
|
||||
import eventHub from '~/pipelines/event_hub';
|
||||
import { stageReply } from './mock_data';
|
||||
|
||||
describe('Pipelines stage component', () => {
|
||||
let StageComponent;
|
||||
let component;
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
|
||||
StageComponent = Vue.extend(stage);
|
||||
|
||||
component = mountComponent(StageComponent, {
|
||||
stage: {
|
||||
status: {
|
||||
group: 'success',
|
||||
icon: 'status_success',
|
||||
title: 'success',
|
||||
},
|
||||
dropdown_path: 'path.json',
|
||||
},
|
||||
updateDropdown: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
component.$destroy();
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
it('should render a dropdown with the status icon', () => {
|
||||
expect(component.$el.getAttribute('class')).toEqual('dropdown');
|
||||
expect(component.$el.querySelector('svg')).toBeDefined();
|
||||
expect(component.$el.querySelector('button').getAttribute('data-toggle')).toEqual('dropdown');
|
||||
});
|
||||
|
||||
describe('with successful request', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet('path.json').reply(200, stageReply);
|
||||
});
|
||||
|
||||
it('should render the received data and emit `clickedDropdown` event', done => {
|
||||
spyOn(eventHub, '$emit');
|
||||
component.$el.querySelector('button').click();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-builds-dropdown-container ul').textContent.trim(),
|
||||
).toContain(stageReply.latest_statuses[0].name);
|
||||
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when request fails', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet('path.json').reply(500);
|
||||
});
|
||||
|
||||
it('should close the dropdown', () => {
|
||||
component.$el.click();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(component.$el.classList.contains('open')).toEqual(false);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update endpoint correctly', () => {
|
||||
beforeEach(() => {
|
||||
const copyStage = Object.assign({}, stageReply);
|
||||
copyStage.latest_statuses[0].name = 'this is the updated content';
|
||||
mock.onGet('bar.json').reply(200, copyStage);
|
||||
});
|
||||
|
||||
it('should update the stage to request the new endpoint provided', done => {
|
||||
component.stage = {
|
||||
status: {
|
||||
group: 'running',
|
||||
icon: 'status_running',
|
||||
title: 'running',
|
||||
},
|
||||
dropdown_path: 'bar.json',
|
||||
};
|
||||
|
||||
Vue.nextTick(() => {
|
||||
component.$el.querySelector('button').click();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-builds-dropdown-container ul').textContent.trim(),
|
||||
).toContain('this is the updated content');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('pipelineActionRequestComplete', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet('path.json').reply(200, stageReply);
|
||||
|
||||
mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
|
||||
});
|
||||
|
||||
describe('within pipeline table', () => {
|
||||
it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', done => {
|
||||
spyOn(eventHub, '$emit');
|
||||
|
||||
component.type = 'PIPELINES_TABLE';
|
||||
component.$el.querySelector('button').click();
|
||||
|
||||
setTimeout(() => {
|
||||
component.$el.querySelector('.js-ci-action').click();
|
||||
setTimeout(() => {
|
||||
component
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
}, 0);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
|||
|
||||
describe Email do
|
||||
describe 'validations' do
|
||||
it_behaves_like 'an object with email-formated attributes', :email do
|
||||
it_behaves_like 'an object with RFC3696 compliant email-formated attributes', :email do
|
||||
subject { build(:email) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ describe User, :do_not_mock_admin_mode do
|
|||
subject { build(:user) }
|
||||
end
|
||||
|
||||
it_behaves_like 'an object with email-formated attributes', :public_email, :notification_email do
|
||||
it_behaves_like 'an object with RFC3696 compliant email-formated attributes', :public_email, :notification_email do
|
||||
subject { build(:user).tap { |user| user.emails << build(:email, email: email_value) } }
|
||||
end
|
||||
|
||||
|
|
@ -916,7 +916,6 @@ describe User, :do_not_mock_admin_mode do
|
|||
user.tap { |u| u.update!(email: new_email) }.reload
|
||||
end.to change(user, :unconfirmed_email).to(new_email)
|
||||
end
|
||||
|
||||
it 'does not change :notification_email' do
|
||||
expect do
|
||||
user.tap { |u| u.update!(email: new_email) }.reload
|
||||
|
|
|
|||
|
|
@ -44,3 +44,44 @@ RSpec.shared_examples 'an object with email-formated attributes' do |*attributes
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'an object with RFC3696 compliant email-formated attributes' do |*attributes|
|
||||
attributes.each do |attribute|
|
||||
describe "specifically its :#{attribute} attribute" do
|
||||
%w[
|
||||
info@example.com
|
||||
info+test@example.com
|
||||
o'reilly@example.com
|
||||
].each do |valid_email|
|
||||
context "with a value of '#{valid_email}'" do
|
||||
let(:email_value) { valid_email }
|
||||
|
||||
it 'is valid' do
|
||||
subject.send("#{attribute}=", valid_email)
|
||||
|
||||
expect(subject).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
%w[
|
||||
foobar
|
||||
test@test@example.com
|
||||
test.test.@example.com
|
||||
.test.test@example.com
|
||||
mailto:test@example.com
|
||||
lol!'+=?><#$%^&*()@gmail.com
|
||||
].each do |invalid_email|
|
||||
context "with a value of '#{invalid_email}'" do
|
||||
let(:email_value) { invalid_email }
|
||||
|
||||
it 'is invalid' do
|
||||
subject.send("#{attribute}=", invalid_email)
|
||||
|
||||
expect(subject).to be_invalid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue