Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
c8a07385
Commit
c8a07385
authored
Jun 10, 2020
by
Nicolò Maria Mezzopera
Committed by
Mark Florian
Jun 10, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add tags loader component
- new component - unit tests - snapshots
parent
e28e7298
Changes
19
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
843 additions
and
477 deletions
+843
-477
app/assets/javascripts/registry/explorer/components/details_page/empty_tags_state.vue
...try/explorer/components/details_page/empty_tags_state.vue
+33
-0
app/assets/javascripts/registry/explorer/components/details_page/tags_loader.vue
...registry/explorer/components/details_page/tags_loader.vue
+34
-0
app/assets/javascripts/registry/explorer/components/details_page/tags_table.vue
.../registry/explorer/components/details_page/tags_table.vue
+210
-0
app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
...registry/explorer/components/list_page/image_list_row.vue
+1
-1
app/assets/javascripts/registry/explorer/pages/details.vue
app/assets/javascripts/registry/explorer/pages/details.vue
+19
-224
app/assets/javascripts/registry/explorer/stores/getters.js
app/assets/javascripts/registry/explorer/stores/getters.js
+0
-6
spec/features/groups/container_registry_spec.rb
spec/features/groups/container_registry_spec.rb
+1
-1
spec/features/projects/container_registry_spec.rb
spec/features/projects/container_registry_spec.rb
+1
-1
spec/frontend/registry/explorer/components/details_page/__snapshots__/tags_loader_spec.js.snap
...nents/details_page/__snapshots__/tags_loader_spec.js.snap
+63
-0
spec/frontend/registry/explorer/components/details_page/delete_alert_spec.js
...try/explorer/components/details_page/delete_alert_spec.js
+5
-0
spec/frontend/registry/explorer/components/details_page/delete_modal_spec.js
...try/explorer/components/details_page/delete_modal_spec.js
+5
-0
spec/frontend/registry/explorer/components/details_page/details_header_spec.js
...y/explorer/components/details_page/details_header_spec.js
+5
-0
spec/frontend/registry/explorer/components/details_page/empty_tags_state.js
...stry/explorer/components/details_page/empty_tags_state.js
+43
-0
spec/frontend/registry/explorer/components/details_page/tags_loader_spec.js
...stry/explorer/components/details_page/tags_loader_spec.js
+49
-0
spec/frontend/registry/explorer/components/details_page/tags_table_spec.js
...istry/explorer/components/details_page/tags_table_spec.js
+287
-0
spec/frontend/registry/explorer/components/list_page/image_list_spec.js
...registry/explorer/components/list_page/image_list_spec.js
+5
-0
spec/frontend/registry/explorer/pages/details_spec.js
spec/frontend/registry/explorer/pages/details_spec.js
+61
-215
spec/frontend/registry/explorer/stores/getters_spec.js
spec/frontend/registry/explorer/stores/getters_spec.js
+0
-29
spec/frontend/registry/explorer/stubs.js
spec/frontend/registry/explorer/stubs.js
+21
-0
No files found.
app/assets/javascripts/registry/explorer/components/details_page/empty_tags_state.vue
0 → 100644
View file @
c8a07385
<
script
>
import
{
GlEmptyState
}
from
'
@gitlab/ui
'
;
import
{
EMPTY_IMAGE_REPOSITORY_TITLE
,
EMPTY_IMAGE_REPOSITORY_MESSAGE
,
}
from
'
../../constants/index
'
;
export
default
{
components
:
{
GlEmptyState
,
},
props
:
{
noContainersImage
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
i18n
:
{
EMPTY_IMAGE_REPOSITORY_TITLE
,
EMPTY_IMAGE_REPOSITORY_MESSAGE
,
},
};
</
script
>
<
template
>
<gl-empty-state
:title=
"$options.i18n.EMPTY_IMAGE_REPOSITORY_TITLE"
:svg-path=
"noContainersImage"
:description=
"$options.i18n.EMPTY_IMAGE_REPOSITORY_MESSAGE"
class=
"gl-mx-auto gl-my-0"
/>
</
template
>
app/assets/javascripts/registry/explorer/components/details_page/tags_loader.vue
0 → 100644
View file @
c8a07385
<
script
>
import
{
GlSkeletonLoader
}
from
'
@gitlab/ui
'
;
export
default
{
components
:
{
GlSkeletonLoader
,
},
loader
:
{
repeat
:
10
,
width
:
1000
,
height
:
40
,
},
};
</
script
>
<
template
>
<div>
<gl-skeleton-loader
v-for=
"index in $options.loader.repeat"
:key=
"index"
:width=
"$options.loader.width"
:height=
"$options.loader.height"
preserve-aspect-ratio=
"xMinYMax meet"
>
<rect
width=
"15"
x=
"0"
y=
"12.5"
height=
"15"
rx=
"4"
/>
<rect
width=
"250"
x=
"25"
y=
"10"
height=
"20"
rx=
"4"
/>
<circle
cx=
"290"
cy=
"20"
r=
"10"
/>
<rect
width=
"100"
x=
"315"
y=
"10"
height=
"20"
rx=
"4"
/>
<rect
width=
"100"
x=
"500"
y=
"10"
height=
"20"
rx=
"4"
/>
<rect
width=
"100"
x=
"630"
y=
"10"
height=
"20"
rx=
"4"
/>
<rect
x=
"960"
y=
"0"
width=
"40"
height=
"40"
rx=
"4"
/>
</gl-skeleton-loader>
</div>
</
template
>
app/assets/javascripts/registry/explorer/components/details_page/tags_table.vue
0 → 100644
View file @
c8a07385
<
script
>
import
{
GlTable
,
GlFormCheckbox
,
GlButton
,
GlTooltipDirective
}
from
'
@gitlab/ui
'
;
import
{
n__
}
from
'
~/locale
'
;
import
ClipboardButton
from
'
~/vue_shared/components/clipboard_button.vue
'
;
import
{
numberToHumanSize
}
from
'
~/lib/utils/number_utils
'
;
import
timeagoMixin
from
'
~/vue_shared/mixins/timeago
'
;
import
{
LIST_KEY_TAG
,
LIST_KEY_IMAGE_ID
,
LIST_KEY_SIZE
,
LIST_KEY_LAST_UPDATED
,
LIST_KEY_ACTIONS
,
LIST_KEY_CHECKBOX
,
LIST_LABEL_TAG
,
LIST_LABEL_IMAGE_ID
,
LIST_LABEL_SIZE
,
LIST_LABEL_LAST_UPDATED
,
REMOVE_TAGS_BUTTON_TITLE
,
REMOVE_TAG_BUTTON_TITLE
,
}
from
'
../../constants/index
'
;
export
default
{
components
:
{
GlTable
,
GlFormCheckbox
,
GlButton
,
ClipboardButton
,
},
directives
:
{
GlTooltip
:
GlTooltipDirective
,
},
mixins
:
[
timeagoMixin
],
props
:
{
tags
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
isLoading
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
isDesktop
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
i18n
:
{
REMOVE_TAGS_BUTTON_TITLE
,
REMOVE_TAG_BUTTON_TITLE
,
},
data
()
{
return
{
selectedItems
:
[],
};
},
computed
:
{
fields
()
{
const
tagClass
=
this
.
isDesktop
?
'
w-25
'
:
''
;
const
tagInnerClass
=
this
.
isDesktop
?
'
mw-m
'
:
'
gl-justify-content-end
'
;
return
[
{
key
:
LIST_KEY_CHECKBOX
,
label
:
''
,
class
:
'
gl-w-16
'
},
{
key
:
LIST_KEY_TAG
,
label
:
LIST_LABEL_TAG
,
class
:
`
${
tagClass
}
js-tag-column`
,
innerClass
:
tagInnerClass
,
},
{
key
:
LIST_KEY_IMAGE_ID
,
label
:
LIST_LABEL_IMAGE_ID
},
{
key
:
LIST_KEY_SIZE
,
label
:
LIST_LABEL_SIZE
},
{
key
:
LIST_KEY_LAST_UPDATED
,
label
:
LIST_LABEL_LAST_UPDATED
},
{
key
:
LIST_KEY_ACTIONS
,
label
:
''
},
].
filter
(
f
=>
f
.
key
!==
LIST_KEY_CHECKBOX
||
this
.
isDesktop
);
},
tagsNames
()
{
return
this
.
tags
.
map
(
t
=>
t
.
name
);
},
selectAllChecked
()
{
return
this
.
selectedItems
.
length
===
this
.
tags
.
length
&&
this
.
tags
.
length
>
0
;
},
},
watch
:
{
tagsNames
:
{
immediate
:
false
,
handler
(
tagsNames
)
{
this
.
selectedItems
=
this
.
selectedItems
.
filter
(
t
=>
tagsNames
.
includes
(
t
));
},
},
},
methods
:
{
formatSize
(
size
)
{
return
numberToHumanSize
(
size
);
},
layers
(
layers
)
{
return
layers
?
n__
(
'
%d layer
'
,
'
%d layers
'
,
layers
)
:
''
;
},
onSelectAllChange
()
{
if
(
this
.
selectAllChecked
)
{
this
.
selectedItems
=
[];
}
else
{
this
.
selectedItems
=
this
.
tags
.
map
(
x
=>
x
.
name
);
}
},
updateSelectedItems
(
name
)
{
const
delIndex
=
this
.
selectedItems
.
findIndex
(
x
=>
x
===
name
);
if
(
delIndex
>
-
1
)
{
this
.
selectedItems
.
splice
(
delIndex
,
1
);
}
else
{
this
.
selectedItems
.
push
(
name
);
}
},
},
};
</
script
>
<
template
>
<gl-table
:items=
"tags"
:fields=
"fields"
:stacked=
"!isDesktop"
show-empty
:busy=
"isLoading"
>
<template
v-if=
"isDesktop"
#head
(
checkbox
)
>
<gl-form-checkbox
data-testid=
"mainCheckbox"
:checked=
"selectAllChecked"
@
change=
"onSelectAllChange"
/>
</
template
>
<
template
#head
(
actions
)
>
<span
class=
"gl-display-flex gl-justify-content-end"
>
<gl-button
v-gl-tooltip
data-testid=
"bulkDeleteButton"
:disabled=
"!selectedItems || selectedItems.length === 0"
icon=
"remove"
variant=
"danger"
:title=
"$options.i18n.REMOVE_TAGS_BUTTON_TITLE"
:aria-label=
"$options.i18n.REMOVE_TAGS_BUTTON_TITLE"
@
click=
"$emit('delete', selectedItems)"
/>
</span>
</
template
>
<
template
#cell(checkbox)=
"{item}"
>
<gl-form-checkbox
data-testid=
"rowCheckbox"
:checked=
"selectedItems.includes(item.name)"
@
change=
"updateSelectedItems(item.name)"
/>
</
template
>
<
template
#cell(name)=
"{item, field}"
>
<div
data-testid=
"rowName"
:class=
"[field.innerClass, 'gl-display-flex']"
>
<span
v-gl-tooltip
data-testid=
"rowNameText"
:title=
"item.name"
class=
"gl-text-overflow-ellipsis gl-overflow-hidden gl-white-space-nowrap"
>
{{
item
.
name
}}
</span>
<clipboard-button
v-if=
"item.location"
data-testid=
"rowClipboardButton"
:title=
"item.location"
:text=
"item.location"
css-class=
"btn-default btn-transparent btn-clipboard"
/>
</div>
</
template
>
<
template
#cell(short_revision)=
"{value}"
>
<span
data-testid=
"rowShortRevision"
>
{{
value
}}
</span>
</
template
>
<
template
#cell(total_size)=
"{item}"
>
<span
data-testid=
"rowSize"
>
{{
formatSize
(
item
.
total_size
)
}}
<template
v-if=
"item.total_size && item.layers"
>
·
</
template
>
{{ layers(item.layers) }}
</span>
</template>
<
template
#cell(created_at)=
"{value}"
>
<span
v-gl-tooltip
data-testid=
"rowTime"
:title=
"tooltipTitle(value)"
>
{{
timeFormatted
(
value
)
}}
</span>
</
template
>
<
template
#cell(actions)=
"{item}"
>
<span
class=
"gl-display-flex gl-justify-content-end"
>
<gl-button
data-testid=
"singleDeleteButton"
:title=
"$options.i18n.REMOVE_TAG_BUTTON_TITLE"
:aria-label=
"$options.i18n.REMOVE_TAG_BUTTON_TITLE"
:disabled=
"!item.destroy_path"
variant=
"danger"
icon=
"remove"
category=
"secondary"
@
click=
"$emit('delete', [item.name])"
/>
</span>
</
template
>
<
template
#empty
>
<slot
name=
"empty"
></slot>
</
template
>
<
template
#table-busy
>
<slot
name=
"loader"
></slot>
</
template
>
</gl-table>
</template>
app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
View file @
c8a07385
...
...
@@ -125,7 +125,7 @@ export default {
:disabled=
"disabledDelete"
:title=
"$options.i18n.REMOVE_REPOSITORY_LABEL"
:aria-label=
"$options.i18n.REMOVE_REPOSITORY_LABEL"
c
lass=
"btn-inverted
"
c
ategory=
"secondary
"
variant=
"danger"
icon=
"remove"
@
click=
"$emit('delete', item)"
...
...
app/assets/javascripts/registry/explorer/pages/details.vue
View file @
c8a07385
This diff is collapsed.
Click to expand it.
app/assets/javascripts/registry/explorer/stores/getters.js
View file @
c8a07385
export
const
tags
=
state
=>
{
// to show the loader inside the table we need to pass an empty array to gl-table whenever the table is loading
// this is to take in account isLoading = true and state.tags =[1,2,3] during pagination and delete
return
state
.
isLoading
?
[]
:
state
.
tags
;
};
export
const
dockerBuildCommand
=
state
=>
{
/* eslint-disable @gitlab/require-i18n-strings */
return
`docker build -t
${
state
.
config
.
repositoryUrl
}
.`
;
...
...
spec/features/groups/container_registry_spec.rb
View file @
c8a07385
...
...
@@ -75,7 +75,7 @@ describe 'Container Registry', :js do
expect
(
service
).
to
receive
(
:execute
).
with
(
container_repository
)
{
{
status: :success
}
}
expect
(
Projects
::
ContainerRepository
::
DeleteTagsService
).
to
receive
(
:new
).
with
(
container_repository
.
project
,
user
,
tags:
[
'latest'
])
{
service
}
click_on
(
class:
'js-delete-registry'
)
first
(
'[data-testid="singleDeleteButton"]'
).
click
expect
(
find
(
'.modal .modal-title'
)).
to
have_content
_
(
'Remove tag'
)
find
(
'.modal .modal-footer .btn-danger'
).
click
end
...
...
spec/features/projects/container_registry_spec.rb
View file @
c8a07385
...
...
@@ -84,7 +84,7 @@ describe 'Container Registry', :js do
expect
(
service
).
to
receive
(
:execute
).
with
(
container_repository
)
{
{
status: :success
}
}
expect
(
Projects
::
ContainerRepository
::
DeleteTagsService
).
to
receive
(
:new
).
with
(
container_repository
.
project
,
user
,
tags:
[
'1'
])
{
service
}
first
(
'
.js-delete-registry
'
).
click
first
(
'
[data-testid="singleDeleteButton"]
'
).
click
expect
(
find
(
'.modal .modal-title'
)).
to
have_content
_
(
'Remove tag'
)
find
(
'.modal .modal-footer .btn-danger'
).
click
end
...
...
spec/frontend/registry/explorer/components/details_page/__snapshots__/tags_loader_spec.js.snap
0 → 100644
View file @
c8a07385
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TagsLoader component has the correct markup 1`] = `
<div>
<div
preserve-aspect-ratio="xMinYMax meet"
>
<rect
height="15"
rx="4"
width="15"
x="0"
y="12.5"
/>
<rect
height="20"
rx="4"
width="250"
x="25"
y="10"
/>
<circle
cx="290"
cy="20"
r="10"
/>
<rect
height="20"
rx="4"
width="100"
x="315"
y="10"
/>
<rect
height="20"
rx="4"
width="100"
x="500"
y="10"
/>
<rect
height="20"
rx="4"
width="100"
x="630"
y="10"
/>
<rect
height="40"
rx="4"
width="40"
x="960"
y="0"
/>
</div>
</div>
`;
spec/frontend/registry/explorer/components/details_page/delete_alert_spec.js
View file @
c8a07385
...
...
@@ -19,6 +19,11 @@ describe('Delete alert', () => {
wrapper
=
shallowMount
(
component
,
{
stubs
:
{
GlSprintf
},
propsData
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
describe
(
'
when deleteAlertType is null
'
,
()
=>
{
it
(
'
does not show the alert
'
,
()
=>
{
mountComponent
();
...
...
spec/frontend/registry/explorer/components/details_page/delete_modal_spec.js
View file @
c8a07385
...
...
@@ -23,6 +23,11 @@ describe('Delete Modal', () => {
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
it
(
'
contains a GlModal
'
,
()
=>
{
mountComponent
();
expect
(
findModal
().
exists
()).
toBe
(
true
);
...
...
spec/frontend/registry/explorer/components/details_page/details_header_spec.js
View file @
c8a07385
...
...
@@ -15,6 +15,11 @@ describe('Details Header', () => {
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
it
(
'
has the correct title
'
,
()
=>
{
mountComponent
();
expect
(
wrapper
.
text
()).
toMatchInterpolatedText
(
DETAILS_PAGE_TITLE
);
...
...
spec/frontend/registry/explorer/components/details_page/empty_tags_state.js
0 → 100644
View file @
c8a07385
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlEmptyState
}
from
'
@gitlab/ui
'
;
import
component
from
'
~/registry/explorer/components/details_page/empty_tags_state.vue
'
;
import
{
EMPTY_IMAGE_REPOSITORY_TITLE
,
EMPTY_IMAGE_REPOSITORY_MESSAGE
,
}
from
'
~/registry/explorer/constants
'
;
describe
(
'
EmptyTagsState component
'
,
()
=>
{
let
wrapper
;
const
findEmptyState
=
()
=>
wrapper
.
find
(
GlEmptyState
);
const
mountComponent
=
()
=>
{
wrapper
=
shallowMount
(
component
,
{
stubs
:
{
GlEmptyState
,
},
propsData
:
{
noContainersImage
:
'
foo
'
,
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
it
(
'
contains gl-empty-state
'
,
()
=>
{
mountComponent
();
expect
(
findEmptyState
().
exist
()).
toBe
(
true
);
});
it
(
'
has the correct props
'
,
()
=>
{
mountComponent
();
expect
(
findEmptyState
().
props
()).
toMatchObject
({
title
:
EMPTY_IMAGE_REPOSITORY_TITLE
,
description
:
EMPTY_IMAGE_REPOSITORY_MESSAGE
,
svgPath
:
'
foo
'
,
});
});
});
spec/frontend/registry/explorer/components/details_page/tags_loader_spec.js
0 → 100644
View file @
c8a07385
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
component
from
'
~/registry/explorer/components/details_page/tags_loader.vue
'
;
import
{
GlSkeletonLoader
}
from
'
../../stubs
'
;
describe
(
'
TagsLoader component
'
,
()
=>
{
let
wrapper
;
const
findGlSkeletonLoaders
=
()
=>
wrapper
.
findAll
(
GlSkeletonLoader
);
const
mountComponent
=
()
=>
{
wrapper
=
shallowMount
(
component
,
{
stubs
:
{
GlSkeletonLoader
,
},
// set the repeat to 1 to avoid a long and verbose snapshot
loader
:
{
...
component
.
loader
,
repeat
:
1
,
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
it
(
'
produces the correct amount of loaders
'
,
()
=>
{
mountComponent
();
expect
(
findGlSkeletonLoaders
().
length
).
toBe
(
1
);
});
it
(
'
has the correct props
'
,
()
=>
{
mountComponent
();
expect
(
findGlSkeletonLoaders
()
.
at
(
0
)
.
props
(),
).
toMatchObject
({
width
:
component
.
loader
.
width
,
height
:
component
.
loader
.
height
,
});
});
it
(
'
has the correct markup
'
,
()
=>
{
mountComponent
();
expect
(
wrapper
.
element
).
toMatchSnapshot
();
});
});
spec/frontend/registry/explorer/components/details_page/tags_table_spec.js
0 → 100644
View file @
c8a07385
import
{
mount
}
from
'
@vue/test-utils
'
;
import
stubChildren
from
'
helpers/stub_children
'
;
import
component
from
'
~/registry/explorer/components/details_page/tags_table.vue
'
;
import
{
tagsListResponse
}
from
'
../../mock_data
'
;
describe
(
'
tags_table
'
,
()
=>
{
let
wrapper
;
const
tags
=
[...
tagsListResponse
.
data
];
const
findMainCheckbox
=
()
=>
wrapper
.
find
(
'
[data-testid="mainCheckbox"]
'
);
const
findFirstRowItem
=
testid
=>
wrapper
.
find
(
`[data-testid="
${
testid
}
"]`
);
const
findBulkDeleteButton
=
()
=>
wrapper
.
find
(
'
[data-testid="bulkDeleteButton"]
'
);
const
findAllDeleteButtons
=
()
=>
wrapper
.
findAll
(
'
[data-testid="singleDeleteButton"]
'
);
const
findAllCheckboxes
=
()
=>
wrapper
.
findAll
(
'
[data-testid="rowCheckbox"]
'
);
const
findCheckedCheckboxes
=
()
=>
findAllCheckboxes
().
filter
(
c
=>
c
.
attributes
(
'
checked
'
));
const
findFirsTagColumn
=
()
=>
wrapper
.
find
(
'
.js-tag-column
'
);
const
findFirstTagNameText
=
()
=>
wrapper
.
find
(
'
[data-testid="rowNameText"]
'
);
const
findLoaderSlot
=
()
=>
wrapper
.
find
(
'
[data-testid="loaderSlot"]
'
);
const
findEmptySlot
=
()
=>
wrapper
.
find
(
'
[data-testid="emptySlot"]
'
);
const
mountComponent
=
(
propsData
=
{
tags
,
isDesktop
:
true
})
=>
{
wrapper
=
mount
(
component
,
{
stubs
:
{
...
stubChildren
(
component
),
GlTable
:
false
,
},
propsData
,
slots
:
{
loader
:
'
<div data-testid="loaderSlot"></div>
'
,
empty
:
'
<div data-testid="emptySlot"></div>
'
,
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
it
.
each
([
'
rowCheckbox
'
,
'
rowName
'
,
'
rowShortRevision
'
,
'
rowSize
'
,
'
rowTime
'
,
'
singleDeleteButton
'
,
])(
'
%s exist in the table
'
,
element
=>
{
mountComponent
();
expect
(
findFirstRowItem
(
element
).
exists
()).
toBe
(
true
);
});
describe
(
'
header checkbox
'
,
()
=>
{
it
(
'
exists
'
,
()
=>
{
mountComponent
();
expect
(
findMainCheckbox
().
exists
()).
toBe
(
true
);
});
it
(
'
if selected selects all the rows
'
,
()
=>
{
mountComponent
();
findMainCheckbox
().
vm
.
$emit
(
'
change
'
);
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findMainCheckbox
().
attributes
(
'
checked
'
)).
toBeTruthy
();
expect
(
findCheckedCheckboxes
()).
toHaveLength
(
tags
.
length
);
});
});
it
(
'
if deselect deselects all the row
'
,
()
=>
{
mountComponent
();
findMainCheckbox
().
vm
.
$emit
(
'
change
'
);
return
wrapper
.
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
findMainCheckbox
().
attributes
(
'
checked
'
)).
toBeTruthy
();
findMainCheckbox
().
vm
.
$emit
(
'
change
'
);
return
wrapper
.
vm
.
$nextTick
();
})
.
then
(()
=>
{
expect
(
findMainCheckbox
().
attributes
(
'
checked
'
)).
toBe
(
undefined
);
expect
(
findCheckedCheckboxes
()).
toHaveLength
(
0
);
});
});
});
describe
(
'
row checkbox
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
();
});
it
(
'
if selected adds item to selectedItems
'
,
()
=>
{
findFirstRowItem
(
'
rowCheckbox
'
).
vm
.
$emit
(
'
change
'
);
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
wrapper
.
vm
.
selectedItems
).
toEqual
([
tags
[
0
].
name
]);
expect
(
findFirstRowItem
(
'
rowCheckbox
'
).
attributes
(
'
checked
'
)).
toBeTruthy
();
});
});
it
(
'
if deselect remove name from selectedItems
'
,
()
=>
{
wrapper
.
setData
({
selectedItems
:
[
tags
[
0
].
name
]
});
findFirstRowItem
(
'
rowCheckbox
'
).
vm
.
$emit
(
'
change
'
);
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
wrapper
.
vm
.
selectedItems
.
length
).
toBe
(
0
);
expect
(
findFirstRowItem
(
'
rowCheckbox
'
).
attributes
(
'
checked
'
)).
toBe
(
undefined
);
});
});
});
describe
(
'
header delete button
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
();
});
it
(
'
exists
'
,
()
=>
{
expect
(
findBulkDeleteButton
().
exists
()).
toBe
(
true
);
});
it
(
'
is disabled if no item is selected
'
,
()
=>
{
expect
(
findBulkDeleteButton
().
attributes
(
'
disabled
'
)).
toBe
(
'
true
'
);
});
it
(
'
is enabled if at least one item is selected
'
,
()
=>
{
expect
(
findBulkDeleteButton
().
attributes
(
'
disabled
'
)).
toBe
(
'
true
'
);
findFirstRowItem
(
'
rowCheckbox
'
).
vm
.
$emit
(
'
change
'
);
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findBulkDeleteButton
().
attributes
(
'
disabled
'
)).
toBeFalsy
();
});
});
describe
(
'
on click
'
,
()
=>
{
it
(
'
when one item is selected
'
,
()
=>
{
findFirstRowItem
(
'
rowCheckbox
'
).
vm
.
$emit
(
'
change
'
);
findBulkDeleteButton
().
vm
.
$emit
(
'
click
'
);
expect
(
wrapper
.
emitted
(
'
delete
'
)).
toEqual
([[[
'
centos6
'
]]]);
});
it
(
'
when multiple items are selected
'
,
()
=>
{
findMainCheckbox
().
vm
.
$emit
(
'
change
'
);
findBulkDeleteButton
().
vm
.
$emit
(
'
click
'
);
expect
(
wrapper
.
emitted
(
'
delete
'
)).
toEqual
([[
tags
.
map
(
t
=>
t
.
name
)]]);
});
});
});
describe
(
'
row delete button
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
();
});
it
(
'
exists
'
,
()
=>
{
expect
(
findAllDeleteButtons
()
.
at
(
0
)
.
exists
(),
).
toBe
(
true
);
});
it
(
'
is disabled if the item has no destroy_path
'
,
()
=>
{
expect
(
findAllDeleteButtons
()
.
at
(
1
)
.
attributes
(
'
disabled
'
),
).
toBe
(
'
true
'
);
});
it
(
'
on click
'
,
()
=>
{
findAllDeleteButtons
()
.
at
(
0
)
.
vm
.
$emit
(
'
click
'
);
expect
(
wrapper
.
emitted
(
'
delete
'
)).
toEqual
([[[
'
centos6
'
]]]);
});
});
describe
(
'
name cell
'
,
()
=>
{
it
(
'
tag column has a tooltip with the tag name
'
,
()
=>
{
mountComponent
();
expect
(
findFirstTagNameText
().
attributes
(
'
title
'
)).
toBe
(
tagsListResponse
.
data
[
0
].
name
);
});
describe
(
'
on desktop viewport
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
();
});
it
(
'
table header has class w-25
'
,
()
=>
{
expect
(
findFirsTagColumn
().
classes
()).
toContain
(
'
w-25
'
);
});
it
(
'
tag column has the mw-m class
'
,
()
=>
{
expect
(
findFirstRowItem
(
'
rowName
'
).
classes
()).
toContain
(
'
mw-m
'
);
});
});
describe
(
'
on mobile viewport
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
({
tags
,
isDesktop
:
false
});
});
it
(
'
table header does not have class w-25
'
,
()
=>
{
expect
(
findFirsTagColumn
().
classes
()).
not
.
toContain
(
'
w-25
'
);
});
it
(
'
tag column has the gl-justify-content-end class
'
,
()
=>
{
expect
(
findFirstRowItem
(
'
rowName
'
).
classes
()).
toContain
(
'
gl-justify-content-end
'
);
});
});
});
describe
(
'
last updated cell
'
,
()
=>
{
let
timeCell
;
beforeEach
(()
=>
{
mountComponent
();
timeCell
=
findFirstRowItem
(
'
rowTime
'
);
});
it
(
'
displays the time in string format
'
,
()
=>
{
expect
(
timeCell
.
text
()).
toBe
(
'
2 years ago
'
);
});
it
(
'
has a tooltip timestamp
'
,
()
=>
{
expect
(
timeCell
.
attributes
(
'
title
'
)).
toBe
(
'
Sep 19, 2017 1:45pm GMT+0000
'
);
});
});
describe
(
'
empty state slot
'
,
()
=>
{
describe
(
'
when the table is empty
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
({
tags
:
[],
isDesktop
:
true
});
});
it
(
'
does not show table rows
'
,
()
=>
{
expect
(
findFirstTagNameText
().
exists
()).
toBe
(
false
);
});
it
(
'
has the empty state slot
'
,
()
=>
{
expect
(
findEmptySlot
().
exists
()).
toBe
(
true
);
});
});
describe
(
'
when the table is not empty
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
({
tags
,
isDesktop
:
true
});
});
it
(
'
does show table rows
'
,
()
=>
{
expect
(
findFirstTagNameText
().
exists
()).
toBe
(
true
);
});
it
(
'
does not show the empty state
'
,
()
=>
{
expect
(
findEmptySlot
().
exists
()).
toBe
(
false
);
});
});
});
describe
(
'
loader slot
'
,
()
=>
{
describe
(
'
when the data is loading
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
({
isLoading
:
true
,
tags
});
});
it
(
'
show the loader
'
,
()
=>
{
expect
(
findLoaderSlot
().
exists
()).
toBe
(
true
);
});
it
(
'
does not show the table rows
'
,
()
=>
{
expect
(
findFirstTagNameText
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
when the data is not loading
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
({
isLoading
:
false
,
tags
});
});
it
(
'
does not show the loader
'
,
()
=>
{
expect
(
findLoaderSlot
().
exists
()).
toBe
(
false
);
});
it
(
'
shows the table rows
'
,
()
=>
{
expect
(
findFirstTagNameText
().
exists
()).
toBe
(
true
);
});
});
});
});
spec/frontend/registry/explorer/components/list_page/image_list_spec.js
View file @
c8a07385
...
...
@@ -24,6 +24,11 @@ describe('Image List', () => {
mountComponent
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
describe
(
'
list
'
,
()
=>
{
it
(
'
contains one list element for each image
'
,
()
=>
{
expect
(
findRow
().
length
).
toBe
(
imagesListResponse
.
data
.
length
);
...
...
spec/frontend/registry/explorer/pages/details_spec.js
View file @
c8a07385
This diff is collapsed.
Click to expand it.
spec/frontend/registry/explorer/stores/getters_spec.js
View file @
c8a07385
...
...
@@ -2,35 +2,6 @@ import * as getters from '~/registry/explorer/stores/getters';
describe
(
'
Getters RegistryExplorer store
'
,
()
=>
{
let
state
;
const
tags
=
[
'
foo
'
,
'
bar
'
];
describe
(
'
tags
'
,
()
=>
{
describe
(
'
when isLoading is false
'
,
()
=>
{
beforeEach
(()
=>
{
state
=
{
tags
,
isLoading
:
false
,
};
});
it
(
'
returns tags
'
,
()
=>
{
expect
(
getters
.
tags
(
state
)).
toEqual
(
state
.
tags
);
});
});
describe
(
'
when isLoading is true
'
,
()
=>
{
beforeEach
(()
=>
{
state
=
{
tags
,
isLoading
:
true
,
};
});
it
(
'
returns empty array
'
,
()
=>
{
expect
(
getters
.
tags
(
state
)).
toEqual
([]);
});
});
});
describe
.
each
`
getter | prefix | configParameter | suffix
...
...
spec/frontend/registry/explorer/stubs.js
View file @
c8a07385
import
RealTagsTable
from
'
~/registry/explorer/components/details_page/tags_table.vue
'
;
import
RealDeleteModal
from
'
~/registry/explorer/components/details_page/delete_modal.vue
'
;
export
const
GlModal
=
{
template
:
'
<div><slot name="modal-title"></slot><slot></slot><slot name="modal-ok"></slot></div>
'
,
methods
:
{
...
...
@@ -14,3 +17,21 @@ export const RouterLink = {
template
:
`<div><slot></slot></div>`
,
props
:
[
'
to
'
],
};
export
const
TagsTable
=
{
props
:
RealTagsTable
.
props
,
template
:
`<div><slot name="empty"></slot><slot name="loader"></slot></div>`
,
};
export
const
DeleteModal
=
{
template
:
'
<div></div>
'
,
methods
:
{
show
:
jest
.
fn
(),
},
props
:
RealDeleteModal
.
props
,
};
export
const
GlSkeletonLoader
=
{
template
:
`<div><slot></slot></div>`
,
props
:
[
'
width
'
,
'
height
'
],
};
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment