Skip to content
Snippets Groups Projects
Commit 40254b9a authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 22a0d312
No related branches found
No related tags found
No related merge requests found
Showing
with 548 additions and 53 deletions
# frozen_string_literal: true
class MigrateIssueTrackersData < ActiveRecord::Migration[5.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INTERVAL = 3.minutes.to_i
BATCH_SIZE = 5_000
MIGRATION = 'MigrateIssueTrackersSensitiveData'
disable_ddl_transaction!
class Service < ActiveRecord::Base
self.table_name = 'services'
self.inheritance_column = :_type_disabled
include ::EachBatch
end
def up
relation = Service.where(category: 'issue_tracker').where("properties IS NOT NULL AND properties != '{}' AND properties != ''")
queue_background_migration_jobs_by_range_at_intervals(relation,
MIGRATION,
INTERVAL,
batch_size: BATCH_SIZE)
end
def down
# no need
end
end
Loading
Loading
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
 
ActiveRecord::Schema.define(version: 2020_01_14_204949) do
ActiveRecord::Schema.define(version: 2020_01_17_112554) do
 
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
Loading
Loading
@@ -2037,14 +2037,17 @@ ActiveRecord::Schema.define(version: 2020_01_14_204949) do
 
create_table "import_failures", force: :cascade do |t|
t.integer "relation_index"
t.bigint "project_id", null: false
t.bigint "project_id"
t.datetime_with_timezone "created_at", null: false
t.string "relation_key", limit: 64
t.string "exception_class", limit: 128
t.string "correlation_id_value", limit: 128
t.string "exception_message", limit: 255
t.integer "retry_count"
t.integer "group_id"
t.index ["correlation_id_value"], name: "index_import_failures_on_correlation_id_value"
t.index ["project_id"], name: "index_import_failures_on_project_id"
t.index ["group_id"], name: "index_import_failures_on_group_id_not_null", where: "(group_id IS NOT NULL)"
t.index ["project_id"], name: "index_import_failures_on_project_id_not_null", where: "(project_id IS NOT NULL)"
end
 
create_table "index_statuses", id: :serial, force: :cascade do |t|
Loading
Loading
@@ -4645,6 +4648,7 @@ ActiveRecord::Schema.define(version: 2020_01_14_204949) do
add_foreign_key "identities", "saml_providers", name: "fk_aade90f0fc", on_delete: :cascade
add_foreign_key "import_export_uploads", "namespaces", column: "group_id", name: "fk_83319d9721", on_delete: :cascade
add_foreign_key "import_export_uploads", "projects", on_delete: :cascade
add_foreign_key "import_failures", "namespaces", column: "group_id", name: "fk_24b824da43", on_delete: :cascade
add_foreign_key "index_statuses", "projects", name: "fk_74b2492545", on_delete: :cascade
add_foreign_key "insights", "namespaces", on_delete: :cascade
add_foreign_key "insights", "projects", on_delete: :cascade
Loading
Loading
Loading
Loading
@@ -15,6 +15,16 @@ GET /projects/:id/deployments
| `sort` | string | no | Return deployments sorted in `asc` or `desc` order. Default is `asc` |
| `updated_after` | datetime | no | Return deployments updated after the specified date |
| `updated_before` | datetime | no | Return deployments updated before the specified date |
| `environment` | string | no | The name of the environment to filter deployments by |
| `status` | string | no | The status to filter deployments by |
The status attribute can be one of the following values:
- created
- running
- success
- failed
- canceled
 
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/deployments"
Loading
Loading
Loading
Loading
@@ -31,7 +31,14 @@ To create a new blank project on the **New project** page:
1. On the **Blank project** tab, provide the following information:
- The name of your project in the **Project name** field. You can't use
special characters, but you can use spaces, hyphens, underscores or even
emoji.
emoji. When adding the name, the **Project slug** will auto populate.
The slug is what the GitLab instance will use as the URL path to the project.
If you want a different slug, input the project name first,
then change the slug after.
- The path to your project in the **Project slug** field. This is the URL
path for your project that the GitLab instance will use. If the
**Project name** is blank, it will auto populate when you fill in
the **Project slug**.
- The **Project description (optional)** field enables you to enter a
description for your project's dashboard, which will help others
understand what your project is about. Though it's not required, it's a good
Loading
Loading
Loading
Loading
@@ -407,6 +407,7 @@ GFM will recognize the following:
| merge request | `!123` | `namespace/project!123` | `project!123` |
| snippet | `$123` | `namespace/project$123` | `project$123` |
| epic **(ULTIMATE)** | `&123` | `group1/subgroup&123` | |
| design **(PREMIUM)** | `#123[file.jpg]` or `#123["file.png"]` | `group1/subgroup#123[file.png]` | `project#123[file.png]` |
| label by ID | `~123` | `namespace/project~123` | `project~123` |
| one-word label by name | `~bug` | `namespace/project~bug` | `project~bug` |
| multi-word label by name | `~"feature request"` | `namespace/project~"feature request"` | `project~"feature request"` |
Loading
Loading
Loading
Loading
@@ -37,6 +37,13 @@ Design Management requires that projects are using
[hashed storage](../../../administration/repository_storage_types.html#hashed-storage)
(the default storage type since v10.0).
 
### Feature Flags
- Reference Parsing
Designs support short references in Markdown, but this needs to be enabled by setting
the `:design_management_reference_filter_gfm_pipeline` feature flag.
## Limitations
 
- Files uploaded must have a file extension of either `png`, `jpg`, `jpeg`, `gif`, `bmp`, `tiff` or `ico`.
Loading
Loading
@@ -137,3 +144,32 @@ Different discussions have different badge numbers:
 
From GitLab 12.5 on, new annotations will be outputted to the issue activity,
so that everyone involved can participate in the discussion.
## References
GitLab Flavored Markdown supports references to designs. The syntax for this is:
`#123[file.jpg]` - the issue reference, with the filename in square braces
File names may contain a variety of odd characters, so two escaping mechanisms are supported:
### Quoting
File names may be quoted with double quotation marks, eg:
`#123["file.jpg"]`
This is useful if, for instance, your filename has square braces in its name. In this scheme, all
double quotation marks in the file name need to be escaped with backslashes, and backslashes need
to be escaped likewise:
`#123["with with \"quote\" marks and a backslash \\.png"]`
### Base64 Encoding
In the case of file names that include HTML elements, you will need to escape these names to avoid
them being processed as HTML literals. To do this, we support base64 encoding, eg.
The file `<a>.jpg` can be referenced as `#123[base64:PGE+LmpwZwo=]`
Obviously we would advise against using such filenames.
Loading
Loading
@@ -21,6 +21,14 @@ module API
optional :sort, type: String, values: DeploymentsFinder::ALLOWED_SORT_DIRECTIONS, default: DeploymentsFinder::DEFAULT_SORT_DIRECTION, desc: 'Sort by asc (ascending) or desc (descending)'
optional :updated_after, type: DateTime, desc: 'Return deployments updated after the specified date'
optional :updated_before, type: DateTime, desc: 'Return deployments updated before the specified date'
optional :environment,
type: String,
desc: 'The name of the environment to filter deployments by'
optional :status,
type: String,
values: Deployment.statuses.keys,
desc: 'The status to filter deployments by'
end
 
get ':id/deployments' do
Loading
Loading
Loading
Loading
@@ -43,15 +43,46 @@ module Banzai
# Returns a String replaced with the return of the block.
def self.references_in(text, pattern = object_class.reference_pattern)
text.gsub(pattern) do |match|
symbol = $~[object_sym]
if object_class.reference_valid?(symbol)
yield match, symbol.to_i, $~[:project], $~[:namespace], $~
if ident = identifier($~)
yield match, ident, $~[:project], $~[:namespace], $~
else
match
end
end
end
 
def self.identifier(match_data)
symbol = symbol_from_match(match_data)
parse_symbol(symbol, match_data) if object_class.reference_valid?(symbol)
end
def identifier(match_data)
self.class.identifier(match_data)
end
def self.symbol_from_match(match)
key = object_sym
match[key] if match.names.include?(key.to_s)
end
# Transform a symbol extracted from the text to a meaningful value
# In most cases these will be integers, so we call #to_i by default
#
# This method has the contract that if a string `ref` refers to a
# record `record`, then `parse_symbol(ref) == record_identifier(record)`.
def self.parse_symbol(symbol, match_data)
symbol.to_i
end
# We assume that most classes are identifying records by ID.
#
# This method has the contract that if a string `ref` refers to a
# record `record`, then `class.parse_symbol(ref) == record_identifier(record)`.
def record_identifier(record)
record.id
end
def object_class
self.class.object_class
end
Loading
Loading
@@ -265,8 +296,10 @@ module Banzai
 
@references_per[parent_type] ||= begin
refs = Hash.new { |hash, key| hash[key] = Set.new }
regex = Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern)
regex = [
object_class.reference_pattern,
object_class.link_reference_pattern
].compact.reduce { |a, b| Regexp.union(a, b) }
 
nodes.each do |node|
node.to_html.scan(regex) do
Loading
Loading
@@ -276,8 +309,9 @@ module Banzai
full_group_path($~[:group])
end
 
symbol = $~[object_sym]
refs[path] << symbol if object_class.reference_valid?(symbol)
if ident = identifier($~)
refs[path] << ident
end
end
end
 
Loading
Loading
Loading
Loading
@@ -37,6 +37,11 @@ module Banzai
end
end
 
# The default behaviour is `#to_i` - we just pass the hash through.
def self.parse_symbol(sha_hash, _match)
sha_hash
end
def url_for_object(commit, project)
h = Gitlab::Routing.url_helpers
 
Loading
Loading
@@ -65,10 +70,6 @@ module Banzai
 
private
 
def record_identifier(record)
record.id
end
def parent_records(parent, ids)
parent.commits_by(oids: ids.to_a)
end
Loading
Loading
Loading
Loading
@@ -5,6 +5,14 @@ module Gitlab
class ApplicationContext
include Gitlab::Utils::LazyAttributes
 
Attribute = Struct.new(:name, :type)
APPLICATION_ATTRIBUTES = [
Attribute.new(:project, Project),
Attribute.new(:namespace, Namespace),
Attribute.new(:user, User)
].freeze
def self.with_context(args, &block)
application_context = new(**args)
Labkit::Context.with_context(application_context.to_lazy_hash, &block)
Loading
Loading
@@ -15,21 +23,36 @@ module Gitlab
Labkit::Context.push(application_context.to_lazy_hash)
end
 
def initialize(user: nil, project: nil, namespace: nil)
@user, @project, @namespace = user, project, namespace
def initialize(**args)
unknown_attributes = args.keys - APPLICATION_ATTRIBUTES.map(&:name)
raise ArgumentError, "#{unknown_attributes} are not known keys" if unknown_attributes.any?
@set_values = args.keys
assign_attributes(args)
end
 
def to_lazy_hash
{ user: -> { username },
project: -> { project_path },
root_namespace: -> { root_namespace_path } }
{}.tap do |hash|
hash[:user] = -> { username } if set_values.include?(:user)
hash[:project] = -> { project_path } if set_values.include?(:project)
hash[:root_namespace] = -> { root_namespace_path } if include_namespace?
end
end
 
private
 
lazy_attr_reader :user, type: User
lazy_attr_reader :project, type: Project
lazy_attr_reader :namespace, type: Namespace
attr_reader :set_values
APPLICATION_ATTRIBUTES.each do |attr|
lazy_attr_reader attr.name, type: attr.type
end
def assign_attributes(values)
values.slice(*APPLICATION_ATTRIBUTES.map(&:name)).each do |name, value|
instance_variable_set("@#{name}", value)
end
end
 
def project_path
project&.full_path
Loading
Loading
@@ -46,5 +69,9 @@ module Gitlab
project&.full_path_components&.first
end
end
def include_namespace?
set_values.include?(:namespace) || set_values.include?(:project)
end
end
end
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# This migration takes all issue trackers
# and move data from properties to data field tables (jira_tracker_data and issue_tracker_data)
class MigrateIssueTrackersSensitiveData
delegate :select_all, :execute, :quote_string, to: :connection
# we need to define this class and set fields encryption
class IssueTrackerData < ApplicationRecord
self.table_name = 'issue_tracker_data'
def self.encryption_options
{
key: Settings.attr_encrypted_db_key_base_32,
encode: true,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm'
}
end
attr_encrypted :project_url, encryption_options
attr_encrypted :issues_url, encryption_options
attr_encrypted :new_issue_url, encryption_options
end
# we need to define this class and set fields encryption
class JiraTrackerData < ApplicationRecord
self.table_name = 'jira_tracker_data'
def self.encryption_options
{
key: Settings.attr_encrypted_db_key_base_32,
encode: true,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm'
}
end
attr_encrypted :url, encryption_options
attr_encrypted :api_url, encryption_options
attr_encrypted :username, encryption_options
attr_encrypted :password, encryption_options
end
def perform(start_id, stop_id)
columns = 'id, properties, title, description, type'
batch_condition = "id >= #{start_id} AND id <= #{stop_id} AND category = 'issue_tracker' \
AND properties IS NOT NULL AND properties != '{}' AND properties != ''"
data_subselect = "SELECT 1 \
FROM jira_tracker_data \
WHERE jira_tracker_data.service_id = services.id \
UNION SELECT 1 \
FROM issue_tracker_data \
WHERE issue_tracker_data.service_id = services.id"
query = "SELECT #{columns} FROM services WHERE #{batch_condition} AND NOT EXISTS (#{data_subselect})"
migrated_ids = []
data_to_insert(query).each do |table, data|
service_ids = data.map { |s| s['service_id'] }
next if service_ids.empty?
migrated_ids += service_ids
Gitlab::Database.bulk_insert(table, data)
end
return if migrated_ids.empty?
move_title_description(migrated_ids)
end
private
def data_to_insert(query)
data = { 'jira_tracker_data' => [], 'issue_tracker_data' => [] }
select_all(query).each do |service|
begin
properties = JSON.parse(service['properties'])
rescue JSON::ParserError
logger.warn(
message: 'Properties data not parsed - invalid json',
service_id: service['id'],
properties: service['properties']
)
next
end
if service['type'] == 'JiraService'
row = data_row(JiraTrackerData, jira_mapping(properties), service)
key = 'jira_tracker_data'
else
row = data_row(IssueTrackerData, issue_tracker_mapping(properties), service)
key = 'issue_tracker_data'
end
data[key] << row if row
end
data
end
def data_row(klass, mapping, service)
base_params = { service_id: service['id'], created_at: Time.current, updated_at: Time.current }
klass.new(mapping).slice(*klass.column_names).compact.merge(base_params)
end
def move_title_description(service_ids)
query = "UPDATE services SET \
title = cast(properties as json)->>'title', \
description = cast(properties as json)->>'description' \
WHERE id IN (#{service_ids.join(',')}) AND title IS NULL AND description IS NULL"
execute(query)
end
def jira_mapping(properties)
{
url: properties['url'],
api_url: properties['api_url'],
username: properties['username'],
password: properties['password']
}
end
def issue_tracker_mapping(properties)
{
project_url: properties['project_url'],
issues_url: properties['issues_url'],
new_issue_url: properties['new_issue_url']
}
end
def connection
@connection ||= ActiveRecord::Base.connection
end
def logger
@logger ||= Gitlab::BackgroundMigration::Logger.build
end
end
end
end
# frozen_string_literal: true
module Gitlab
module ImportExport
class ImportFailureService
RETRIABLE_EXCEPTIONS = [GRPC::DeadlineExceeded, ActiveRecord::QueryCanceled].freeze
attr_reader :importable
def initialize(importable)
@importable = importable
@association = importable.association(:import_failures)
end
def with_retry(relation_key, relation_index)
on_retry = -> (exception, retry_count, *_args) do
log_import_failure(relation_key, relation_index, exception, retry_count)
end
Retriable.with_context(:relation_import, on_retry: on_retry) do
yield
end
end
def log_import_failure(relation_key, relation_index, exception, retry_count = 0)
extra = {
relation_key: relation_key,
relation_index: relation_index,
retry_count: retry_count
}
extra[importable_column_name] = importable.id
Gitlab::ErrorTracking.track_exception(exception, extra)
attributes = {
exception_class: exception.class.to_s,
exception_message: exception.message.truncate(255),
correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id
}.merge(extra)
ImportFailure.create(attributes)
end
private
def importable_column_name
@importable_column_name ||= @association.reflection.foreign_key.to_sym
end
end
end
end
Loading
Loading
@@ -72,25 +72,18 @@ module Gitlab
return if importable_class == Project && group_model?(relation_object)
 
relation_object.assign_attributes(importable_class_sym => @importable)
relation_object.save!
import_failure_service.with_retry(relation_key, relation_index) do
relation_object.save!
end
 
save_id_mapping(relation_key, data_hash, relation_object)
rescue => e
log_import_failure(relation_key, relation_index, e)
import_failure_service.log_import_failure(relation_key, relation_index, e)
end
 
def log_import_failure(relation_key, relation_index, exception)
Gitlab::ErrorTracking.track_exception(exception,
project_id: @importable.id, relation_key: relation_key, relation_index: relation_index)
ImportFailure.create(
project: @importable,
relation_key: relation_key,
relation_index: relation_index,
exception_class: exception.class.to_s,
exception_message: exception.message.truncate(255),
correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id
)
def import_failure_service
@import_failure_service ||= ImportFailureService.new(@importable)
end
 
# Older, serialized CI pipeline exports may only have a
Loading
Loading
Loading
Loading
@@ -25,6 +25,42 @@ describe DeploymentsFinder do
is_expected.to match_array([deployment_1])
end
end
context 'when the environment name is specified' do
let!(:environment1) { create(:environment, project: project) }
let!(:environment2) { create(:environment, project: project) }
let!(:deployment1) do
create(:deployment, project: project, environment: environment1)
end
let!(:deployment2) do
create(:deployment, project: project, environment: environment2)
end
let(:params) { { environment: environment1.name } }
it 'returns deployments for the given environment' do
is_expected.to match_array([deployment1])
end
end
context 'when the deployment status is specified' do
let!(:deployment1) { create(:deployment, :success, project: project) }
let!(:deployment2) { create(:deployment, :failed, project: project) }
let(:params) { { status: 'success' } }
it 'returns deployments for the given environment' do
is_expected.to match_array([deployment1])
end
end
context 'when using an invalid deployment status' do
let(:params) { { status: 'kittens' } }
it 'raises ArgumentError' do
expect { subject }.to raise_error(ArgumentError)
end
end
end
 
describe 'ordering' do
Loading
Loading
Loading
Loading
@@ -143,10 +143,14 @@ describe('ErrorTrackingList', () => {
});
 
it('each error in the list should have an ignore button', () => {
const error = wrapper.findAll('tbody tr');
findErrorListRows().wrappers.forEach(row => {
expect(row.contains('glicon-stub[name="eye-slash"]')).toBe(true);
});
});
 
error.wrappers.forEach((_, index) => {
expect(error.at(index).exists('glicon-stub[name="eye-slash"]')).toBe(true);
it('each error in the list should have a resolve button', () => {
findErrorListRows().wrappers.forEach(row => {
expect(row.contains('glicon-stub[name="check-circle"]')).toBe(true);
});
});
 
Loading
Loading
@@ -231,8 +235,7 @@ describe('ErrorTrackingList', () => {
});
 
it('sends the "ignored" status and error ID', () => {
const ignoreButton = wrapper.find({ ref: 'ignoreError' });
ignoreButton.trigger('click');
wrapper.find({ ref: 'ignoreError' }).trigger('click');
expect(actions.updateStatus).toHaveBeenCalledWith(
expect.anything(),
{
Loading
Loading
@@ -245,6 +248,34 @@ describe('ErrorTrackingList', () => {
});
});
 
describe('When the resolve button on an error is clicked', () => {
beforeEach(() => {
store.state.list.loading = false;
store.state.list.errors = errorsList;
mountComponent({
stubs: {
GlTable: false,
GlLink: false,
GlButton: false,
},
});
});
it('sends "resolved" status and error ID', () => {
wrapper.find({ ref: 'resolveError' }).trigger('click');
expect(actions.updateStatus).toHaveBeenCalledWith(
expect.anything(),
{
endpoint: '/project/test/-/error_tracking/3.json',
redirectUrl: '/error_tracking',
status: 'resolved',
},
undefined,
);
});
});
describe('When error tracking is disabled and user is not allowed to enable it', () => {
beforeEach(() => {
mountComponent({
Loading
Loading
Loading
Loading
@@ -27,6 +27,9 @@ describe('text_utility', () => {
it('should remove underscores and uppercase the first letter', () => {
expect(textUtils.humanize('foo_bar')).toEqual('Foo bar');
});
it('should remove underscores and dashes and uppercase the first letter', () => {
expect(textUtils.humanize('foo_bar-foo', '[_-]')).toEqual('Foo bar foo');
});
});
 
describe('dasherize', () => {
Loading
Loading
@@ -52,14 +55,20 @@ describe('text_utility', () => {
expect(textUtils.slugify(' a new project ')).toEqual('a-new-project');
});
it('should only remove non-allowed special characters', () => {
expect(textUtils.slugify('test!_pro-ject~')).toEqual('test-_pro-ject-');
expect(textUtils.slugify('test!_pro-ject~')).toEqual('test-_pro-ject');
});
it('should squash multiple hypens', () => {
expect(textUtils.slugify('test!!!!_pro-ject~')).toEqual('test-_pro-ject-');
expect(textUtils.slugify('test!!!!_pro-ject~')).toEqual('test-_pro-ject');
});
it('should return empty string if only non-allowed characters', () => {
expect(textUtils.slugify('здрасти')).toEqual('');
});
it('should squash multiple separators', () => {
expect(textUtils.slugify('Test:-)')).toEqual('test');
});
it('should trim any separators from the beginning and end of the slug', () => {
expect(textUtils.slugify('-Test:-)-')).toEqual('test');
});
});
 
describe('stripHtml', () => {
Loading
Loading
@@ -109,6 +118,12 @@ describe('text_utility', () => {
});
});
 
describe('convertToTitleCase', () => {
it('converts sentence case to Sentence Case', () => {
expect(textUtils.convertToTitleCase('hello world')).toBe('Hello World');
});
});
describe('truncateSha', () => {
it('shortens SHAs to 8 characters', () => {
expect(textUtils.truncateSha('verylongsha')).toBe('verylong');
Loading
Loading
Loading
Loading
@@ -29,8 +29,8 @@ describe('Monitoring mutations', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload);
const groups = getGroups();
 
expect(groups[0].key).toBe('response-metrics-nginx-ingress-vts--0');
expect(groups[1].key).toBe('system-metrics-kubernetes--1');
expect(groups[0].key).toBe('response-metrics-nginx-ingress-vts-0');
expect(groups[1].key).toBe('system-metrics-kubernetes-1');
});
it('normalizes values', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload);
Loading
Loading
Loading
Loading
@@ -169,7 +169,7 @@ describe('Release block', () => {
releaseClone.tag_name = 'a dangerous tag name <script>alert("hello")</script>';
 
return factory(releaseClone).then(() => {
expect(wrapper.attributes().id).toBe('a-dangerous-tag-name-script-alert-hello-script-');
expect(wrapper.attributes().id).toBe('a-dangerous-tag-name-script-alert-hello-script');
});
});
 
Loading
Loading
import projectImportGitlab from '~/projects/project_import_gitlab_project';
 
describe('Import Gitlab project', () => {
let projectName;
beforeEach(() => {
projectName = 'project';
window.history.pushState({}, null, `?path=${projectName}`);
const pathName = 'my-project';
const projectName = 'My Project';
const setTestFixtures = url => {
window.history.pushState({}, null, url);
 
setFixtures(`
<input class="js-path-name" />
<input class="js-project-name" />
`);
 
projectImportGitlab();
};
beforeEach(() => {
setTestFixtures(`?name=${projectName}&path=${pathName}`);
});
 
afterEach(() => {
window.history.pushState({}, null, '');
});
 
describe('path name', () => {
describe('project name', () => {
it('should fill in the project name derived from the previously filled project name', () => {
expect(document.querySelector('.js-path-name').value).toEqual(projectName);
expect(document.querySelector('.js-project-name').value).toEqual(projectName);
});
describe('empty path name', () => {
it('derives the path name from the previously filled project name', () => {
const alternateProjectName = 'My Alt Project';
const alternatePathName = 'my-alt-project';
setTestFixtures(`?name=${alternateProjectName}`);
expect(document.querySelector('.js-path-name').value).toEqual(alternatePathName);
});
});
});
describe('path name', () => {
it('should fill in the path name derived from the previously filled path name', () => {
expect(document.querySelector('.js-path-name').value).toEqual(pathName);
});
describe('empty project name', () => {
it('derives the project name from the previously filled path name', () => {
const alternateProjectName = 'My Alt Project';
const alternatePathName = 'my-alt-project';
setTestFixtures(`?path=${alternatePathName}`);
expect(document.querySelector('.js-project-name').value).toEqual(alternateProjectName);
});
});
});
});
Loading
Loading
@@ -172,4 +172,34 @@ describe('New Project', () => {
expect($projectPath.val()).toEqual('my-dash-delimited-awesome-project');
});
});
describe('derivesProjectNameFromSlug', () => {
const dummyProjectPath = 'my-awesome-project';
const dummyProjectName = 'Original Awesome Project';
beforeEach(() => {
projectNew.bindEvents();
$projectPath.val('').change();
});
it('converts slug to humanized project name', () => {
$projectPath.val(dummyProjectPath);
projectNew.onProjectPathChange($projectName, $projectPath);
expect($projectName.val()).toEqual('My Awesome Project');
});
it('does not convert slug to humanized project name if a project name already exists', () => {
$projectName.val(dummyProjectName);
$projectPath.val(dummyProjectPath);
projectNew.onProjectPathChange(
$projectName,
$projectPath,
$projectName.val().trim().length > 0,
);
expect($projectName.val()).toEqual(dummyProjectName);
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment