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
341637eb
Commit
341637eb
authored
Dec 03, 2020
by
Nicolò Maria Mezzopera
Committed by
Natalia Tepluhina
Dec 03, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add graphql queries, mutations and fragments
- queries - mutations - fragments
parent
2f6b9388
Changes
19
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
643 additions
and
236 deletions
+643
-236
app/assets/javascripts/registry/explorer/components/list_page/image_list.vue
...pts/registry/explorer/components/list_page/image_list.vue
+15
-18
app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
...registry/explorer/components/list_page/image_list_row.vue
+24
-10
app/assets/javascripts/registry/explorer/constants/list.js
app/assets/javascripts/registry/explorer/constants/list.js
+3
-2
app/assets/javascripts/registry/explorer/graphql/fragments/container_repository.fragment.graphql
...r/graphql/fragments/container_repository.fragment.graphql
+11
-0
app/assets/javascripts/registry/explorer/graphql/index.js
app/assets/javascripts/registry/explorer/graphql/index.js
+14
-0
app/assets/javascripts/registry/explorer/graphql/mutations/delete_container_repository.graphql
...rer/graphql/mutations/delete_container_repository.graphql
+9
-0
app/assets/javascripts/registry/explorer/graphql/queries/get_group_container_repositories.graphql
.../graphql/queries/get_group_container_repositories.graphql
+23
-0
app/assets/javascripts/registry/explorer/graphql/queries/get_project_container_repositories.graphql
...raphql/queries/get_project_container_repositories.graphql
+23
-0
app/assets/javascripts/registry/explorer/index.js
app/assets/javascripts/registry/explorer/index.js
+2
-0
app/assets/javascripts/registry/explorer/pages/list.vue
app/assets/javascripts/registry/explorer/pages/list.vue
+107
-27
app/views/groups/registry/repositories/index.html.haml
app/views/groups/registry/repositories/index.html.haml
+1
-0
app/views/projects/registry/repositories/index.html.haml
app/views/projects/registry/repositories/index.html.haml
+1
-1
changelogs/unreleased/276432-refactor-container-registry-frontend-to-graphql.yml
...76432-refactor-container-registry-frontend-to-graphql.yml
+5
-0
spec/features/groups/container_registry_spec.rb
spec/features/groups/container_registry_spec.rb
+0
-7
spec/features/projects/container_registry_spec.rb
spec/features/projects/container_registry_spec.rb
+13
-6
spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
...stry/explorer/components/list_page/image_list_row_spec.js
+37
-24
spec/frontend/registry/explorer/components/list_page/image_list_spec.js
...registry/explorer/components/list_page/image_list_spec.js
+41
-19
spec/frontend/registry/explorer/mock_data.js
spec/frontend/registry/explorer/mock_data.js
+105
-21
spec/frontend/registry/explorer/pages/list_spec.js
spec/frontend/registry/explorer/pages/list_spec.js
+209
-101
No files found.
app/assets/javascripts/registry/explorer/components/list_page/image_list.vue
View file @
341637eb
<
script
>
<
script
>
import
{
GlPagination
}
from
'
@gitlab/ui
'
;
import
{
Gl
Keyset
Pagination
}
from
'
@gitlab/ui
'
;
import
ImageListRow
from
'
./image_list_row.vue
'
;
import
ImageListRow
from
'
./image_list_row.vue
'
;
export
default
{
export
default
{
name
:
'
ImageList
'
,
name
:
'
ImageList
'
,
components
:
{
components
:
{
GlPagination
,
Gl
Keyset
Pagination
,
ImageListRow
,
ImageListRow
,
},
},
props
:
{
props
:
{
...
@@ -13,19 +13,14 @@ export default {
...
@@ -13,19 +13,14 @@ export default {
type
:
Array
,
type
:
Array
,
required
:
true
,
required
:
true
,
},
},
pag
ination
:
{
pag
eInfo
:
{
type
:
Object
,
type
:
Object
,
required
:
true
,
required
:
true
,
},
},
},
},
computed
:
{
computed
:
{
currentPage
:
{
showPagination
()
{
get
()
{
return
this
.
pageInfo
.
hasPreviousPage
||
this
.
pageInfo
.
hasNextPage
;
return
this
.
pagination
.
page
;
},
set
(
page
)
{
this
.
$emit
(
'
pageChange
'
,
page
);
},
},
},
},
},
};
};
...
@@ -40,13 +35,15 @@ export default {
...
@@ -40,13 +35,15 @@ export default {
:first=
"index === 0"
:first=
"index === 0"
@
delete=
"$emit('delete', $event)"
@
delete=
"$emit('delete', $event)"
/>
/>
<div
class=
"gl-display-flex gl-justify-content-center"
>
<gl-pagination
<gl-keyset-pagination
v-model=
"currentPage"
v-if=
"showPagination"
:per-page=
"pagination.perPage"
:has-next-page=
"pageInfo.hasNextPage"
:total-items=
"pagination.total"
:has-previous-page=
"pageInfo.hasPreviousPage"
align=
"center"
class=
"gl-mt-3"
class=
"w-100 gl-mt-3"
@
prev=
"$emit('prev-page')"
/>
@
next=
"$emit('next-page')"
/>
</div>
</div>
</div>
</
template
>
</
template
>
app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue
View file @
341637eb
<
script
>
<
script
>
import
{
GlTooltipDirective
,
GlIcon
,
GlSprintf
}
from
'
@gitlab/ui
'
;
import
{
GlTooltipDirective
,
GlIcon
,
GlSprintf
}
from
'
@gitlab/ui
'
;
import
{
n__
}
from
'
~/locale
'
;
import
{
n__
}
from
'
~/locale
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
ClipboardButton
from
'
~/vue_shared/components/clipboard_button.vue
'
;
import
ClipboardButton
from
'
~/vue_shared/components/clipboard_button.vue
'
;
import
ListItem
from
'
~/vue_shared/components/registry/list_item.vue
'
;
import
ListItem
from
'
~/vue_shared/components/registry/list_item.vue
'
;
import
DeleteButton
from
'
../delete_button.vue
'
;
import
DeleteButton
from
'
../delete_button.vue
'
;
...
@@ -11,6 +13,8 @@ import {
...
@@ -11,6 +13,8 @@ import {
REMOVE_REPOSITORY_LABEL
,
REMOVE_REPOSITORY_LABEL
,
ROW_SCHEDULED_FOR_DELETION
,
ROW_SCHEDULED_FOR_DELETION
,
CLEANUP_TIMED_OUT_ERROR_MESSAGE
,
CLEANUP_TIMED_OUT_ERROR_MESSAGE
,
IMAGE_DELETE_SCHEDULED_STATUS
,
IMAGE_FAILED_DELETED_STATUS
,
}
from
'
../../constants/index
'
;
}
from
'
../../constants/index
'
;
export
default
{
export
default
{
...
@@ -38,19 +42,29 @@ export default {
...
@@ -38,19 +42,29 @@ export default {
},
},
computed
:
{
computed
:
{
disabledDelete
()
{
disabledDelete
()
{
return
!
this
.
item
.
destroy_path
||
this
.
item
.
deleting
;
return
!
this
.
item
.
canDelete
||
this
.
deleting
;
},
id
()
{
return
getIdFromGraphQLId
(
this
.
item
.
id
);
},
deleting
()
{
return
this
.
item
.
status
===
IMAGE_DELETE_SCHEDULED_STATUS
;
},
failedDelete
()
{
return
this
.
item
.
status
===
IMAGE_FAILED_DELETED_STATUS
;
},
},
tagsCountText
()
{
tagsCountText
()
{
return
n__
(
return
n__
(
'
ContainerRegistry|%{count} Tag
'
,
'
ContainerRegistry|%{count} Tag
'
,
'
ContainerRegistry|%{count} Tags
'
,
'
ContainerRegistry|%{count} Tags
'
,
this
.
item
.
tags
_c
ount
,
this
.
item
.
tags
C
ount
,
);
);
},
},
warningIconText
()
{
warningIconText
()
{
if
(
this
.
item
.
failedDelete
)
{
if
(
this
.
failedDelete
)
{
return
ASYNC_DELETE_IMAGE_ERROR_MESSAGE
;
return
ASYNC_DELETE_IMAGE_ERROR_MESSAGE
;
}
else
if
(
this
.
item
.
cleanup_policy_started_at
)
{
}
if
(
this
.
item
.
expirationPolicyStartedAt
)
{
return
CLEANUP_TIMED_OUT_ERROR_MESSAGE
;
return
CLEANUP_TIMED_OUT_ERROR_MESSAGE
;
}
}
return
null
;
return
null
;
...
@@ -63,23 +77,23 @@ export default {
...
@@ -63,23 +77,23 @@ export default {
<list-item
<list-item
v-gl-tooltip=
"
{
v-gl-tooltip=
"
{
placement: 'left',
placement: 'left',
disabled: !
item.
deleting,
disabled: !deleting,
title: $options.i18n.ROW_SCHEDULED_FOR_DELETION,
title: $options.i18n.ROW_SCHEDULED_FOR_DELETION,
}"
}"
v-bind="$attrs"
v-bind="$attrs"
:disabled="
item.
deleting"
:disabled="deleting"
>
>
<template
#left-primary
>
<template
#left-primary
>
<router-link
<router-link
class=
"gl-text-body gl-font-weight-bold"
class=
"gl-text-body gl-font-weight-bold"
data-testid=
"details-link"
data-testid=
"details-link"
:to=
"
{ name: 'details', params: { id
: item.id
} }"
:to=
"
{ name: 'details', params: { id } }"
>
>
{{
item
.
path
}}
{{
item
.
path
}}
</router-link>
</router-link>
<clipboard-button
<clipboard-button
v-if=
"item.location"
v-if=
"item.location"
:disabled=
"
item.
deleting"
:disabled=
"deleting"
:text=
"item.location"
:text=
"item.location"
:title=
"item.location"
:title=
"item.location"
category=
"tertiary"
category=
"tertiary"
...
@@ -97,7 +111,7 @@ export default {
...
@@ -97,7 +111,7 @@ export default {
<gl-icon
name=
"tag"
class=
"gl-mr-2"
/>
<gl-icon
name=
"tag"
class=
"gl-mr-2"
/>
<gl-sprintf
:message=
"tagsCountText"
>
<gl-sprintf
:message=
"tagsCountText"
>
<template
#count
>
<template
#count
>
{{
item
.
tags
_c
ount
}}
{{
item
.
tags
C
ount
}}
</
template
>
</
template
>
</gl-sprintf>
</gl-sprintf>
</span>
</span>
...
@@ -106,7 +120,7 @@ export default {
...
@@ -106,7 +120,7 @@ export default {
<delete-button
<delete-button
:title=
"$options.i18n.REMOVE_REPOSITORY_LABEL"
:title=
"$options.i18n.REMOVE_REPOSITORY_LABEL"
:disabled=
"disabledDelete"
:disabled=
"disabledDelete"
:tooltip-disabled=
"
Boolean(item.destroy_path)
"
:tooltip-disabled=
"
item.canDelete
"
:tooltip-title=
"$options.i18n.LIST_DELETE_BUTTON_DISABLED"
:tooltip-title=
"$options.i18n.LIST_DELETE_BUTTON_DISABLED"
@
delete=
"$emit('delete', item)"
@
delete=
"$emit('delete', item)"
/>
/>
...
...
app/assets/javascripts/registry/explorer/constants/list.js
View file @
341637eb
...
@@ -44,5 +44,6 @@ export const EMPTY_RESULT_MESSAGE = s__(
...
@@ -44,5 +44,6 @@ export const EMPTY_RESULT_MESSAGE = s__(
// Parameters
// Parameters
export
const
IMAGE_DELETE_SCHEDULED_STATUS
=
'
delete_scheduled
'
;
export
const
IMAGE_DELETE_SCHEDULED_STATUS
=
'
DELETE_SCHEDULED
'
;
export
const
IMAGE_FAILED_DELETED_STATUS
=
'
delete_failed
'
;
export
const
IMAGE_FAILED_DELETED_STATUS
=
'
DELETE_FAILED
'
;
export
const
GRAPHQL_PAGE_SIZE
=
10
;
app/assets/javascripts/registry/explorer/graphql/fragments/container_repository.fragment.graphql
0 → 100644
View file @
341637eb
fragment
ContainerRepositoryFields
on
ContainerRepository
{
id
name
path
status
location
canDelete
createdAt
tagsCount
expirationPolicyStartedAt
}
app/assets/javascripts/registry/explorer/graphql/index.js
0 → 100644
View file @
341637eb
import
Vue
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
createDefaultClient
from
'
~/lib/graphql
'
;
Vue
.
use
(
VueApollo
);
export
const
apolloProvider
=
new
VueApollo
({
defaultClient
:
createDefaultClient
(
{},
{
assumeImmutableResults
:
true
,
},
),
});
app/assets/javascripts/registry/explorer/graphql/mutations/delete_container_repository.graphql
0 → 100644
View file @
341637eb
mutation
destroyContainerRepository
(
$id
:
ContainerRepositoryID
!)
{
destroyContainerRepository
(
input
:
{
id
:
$id
})
{
containerRepository
{
id
status
}
errors
}
}
app/assets/javascripts/registry/explorer/graphql/queries/get_group_container_repositories.graphql
0 → 100644
View file @
341637eb
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "../fragments/container_repository.fragment.graphql"
query
getProjectContainerRepositories
(
$fullPath
:
ID
!
$name
:
String
$first
:
Int
$last
:
Int
$after
:
String
$before
:
String
)
{
group
(
fullPath
:
$fullPath
)
{
containerRepositoriesCount
containerRepositories
(
name
:
$name
,
after
:
$after
,
before
:
$before
,
first
:
$first
,
last
:
$last
)
{
nodes
{
...
ContainerRepositoryFields
}
pageInfo
{
...
PageInfo
}
}
}
}
app/assets/javascripts/registry/explorer/graphql/queries/get_project_container_repositories.graphql
0 → 100644
View file @
341637eb
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "../fragments/container_repository.fragment.graphql"
query
getProjectContainerRepositories
(
$fullPath
:
ID
!
$name
:
String
$first
:
Int
$last
:
Int
$after
:
String
$before
:
String
)
{
project
(
fullPath
:
$fullPath
)
{
containerRepositoriesCount
containerRepositories
(
name
:
$name
,
after
:
$after
,
before
:
$before
,
first
:
$first
,
last
:
$last
)
{
nodes
{
...
ContainerRepositoryFields
}
pageInfo
{
...
PageInfo
}
}
}
}
app/assets/javascripts/registry/explorer/index.js
View file @
341637eb
...
@@ -5,6 +5,7 @@ import RegistryExplorer from './pages/index.vue';
...
@@ -5,6 +5,7 @@ import RegistryExplorer from './pages/index.vue';
import
RegistryBreadcrumb
from
'
./components/registry_breadcrumb.vue
'
;
import
RegistryBreadcrumb
from
'
./components/registry_breadcrumb.vue
'
;
import
{
createStore
}
from
'
./stores
'
;
import
{
createStore
}
from
'
./stores
'
;
import
createRouter
from
'
./router
'
;
import
createRouter
from
'
./router
'
;
import
{
apolloProvider
}
from
'
./graphql/index
'
;
Vue
.
use
(
Translate
);
Vue
.
use
(
Translate
);
Vue
.
use
(
GlToast
);
Vue
.
use
(
GlToast
);
...
@@ -27,6 +28,7 @@ export default () => {
...
@@ -27,6 +28,7 @@ export default () => {
el
,
el
,
store
,
store
,
router
,
router
,
apolloProvider
,
components
:
{
components
:
{
RegistryExplorer
,
RegistryExplorer
,
},
},
...
...
app/assets/javascripts/registry/explorer/pages/list.vue
View file @
341637eb
<
script
>
<
script
>
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
{
mapState
}
from
'
vuex
'
;
import
{
import
{
GlEmptyState
,
GlEmptyState
,
GlTooltipDirective
,
GlTooltipDirective
,
...
@@ -11,6 +11,7 @@ import {
...
@@ -11,6 +11,7 @@ import {
GlSearchBoxByClick
,
GlSearchBoxByClick
,
}
from
'
@gitlab/ui
'
;
}
from
'
@gitlab/ui
'
;
import
Tracking
from
'
~/tracking
'
;
import
Tracking
from
'
~/tracking
'
;
import
createFlash
from
'
~/flash
'
;
import
ProjectEmptyState
from
'
../components/list_page/project_empty_state.vue
'
;
import
ProjectEmptyState
from
'
../components/list_page/project_empty_state.vue
'
;
import
GroupEmptyState
from
'
../components/list_page/group_empty_state.vue
'
;
import
GroupEmptyState
from
'
../components/list_page/group_empty_state.vue
'
;
...
@@ -18,6 +19,10 @@ import RegistryHeader from '../components/list_page/registry_header.vue';
...
@@ -18,6 +19,10 @@ import RegistryHeader from '../components/list_page/registry_header.vue';
import
ImageList
from
'
../components/list_page/image_list.vue
'
;
import
ImageList
from
'
../components/list_page/image_list.vue
'
;
import
CliCommands
from
'
../components/list_page/cli_commands.vue
'
;
import
CliCommands
from
'
../components/list_page/cli_commands.vue
'
;
import
getProjectContainerRepositories
from
'
../graphql/queries/get_project_container_repositories.graphql
'
;
import
getGroupContainerRepositories
from
'
../graphql/queries/get_group_container_repositories.graphql
'
;
import
deleteContainerRepository
from
'
../graphql/mutations/delete_container_repository.graphql
'
;
import
{
import
{
DELETE_IMAGE_SUCCESS_MESSAGE
,
DELETE_IMAGE_SUCCESS_MESSAGE
,
DELETE_IMAGE_ERROR_MESSAGE
,
DELETE_IMAGE_ERROR_MESSAGE
,
...
@@ -29,6 +34,8 @@ import {
...
@@ -29,6 +34,8 @@ import {
IMAGE_REPOSITORY_LIST_LABEL
,
IMAGE_REPOSITORY_LIST_LABEL
,
EMPTY_RESULT_TITLE
,
EMPTY_RESULT_TITLE
,
EMPTY_RESULT_MESSAGE
,
EMPTY_RESULT_MESSAGE
,
GRAPHQL_PAGE_SIZE
,
FETCH_IMAGES_LIST_ERROR_MESSAGE
,
}
from
'
../constants/index
'
;
}
from
'
../constants/index
'
;
export
default
{
export
default
{
...
@@ -66,21 +73,63 @@ export default {
...
@@ -66,21 +73,63 @@ export default {
EMPTY_RESULT_TITLE
,
EMPTY_RESULT_TITLE
,
EMPTY_RESULT_MESSAGE
,
EMPTY_RESULT_MESSAGE
,
},
},
apollo
:
{
images
:
{
query
()
{
return
this
.
graphQlQuery
;
},
variables
()
{
return
this
.
queryVariables
;
},
update
(
data
)
{
return
data
[
this
.
graphqlResource
]?.
containerRepositories
.
nodes
;
},
result
({
data
})
{
this
.
pageInfo
=
data
[
this
.
graphqlResource
]?.
containerRepositories
?.
pageInfo
;
this
.
containerRepositoriesCount
=
data
[
this
.
graphqlResource
]?.
containerRepositoriesCount
;
},
error
()
{
createFlash
({
message
:
FETCH_IMAGES_LIST_ERROR_MESSAGE
});
},
},
},
data
()
{
data
()
{
return
{
return
{
images
:
[],
pageInfo
:
{},
containerRepositoriesCount
:
0
,
itemToDelete
:
{},
itemToDelete
:
{},
deleteAlertType
:
null
,
deleteAlertType
:
null
,
search
:
null
,
searchValue
:
null
,
isEmpty
:
false
,
name
:
null
,
mutationLoading
:
false
,
};
};
},
},
computed
:
{
computed
:
{
...
mapState
([
'
config
'
,
'
isLoading
'
,
'
images
'
,
'
pagination
'
]),
...
mapState
([
'
config
'
]),
graphqlResource
()
{
return
this
.
config
.
isGroupPage
?
'
group
'
:
'
project
'
;
},
graphQlQuery
()
{
return
this
.
config
.
isGroupPage
?
getGroupContainerRepositories
:
getProjectContainerRepositories
;
},
queryVariables
()
{
return
{
name
:
this
.
name
,
fullPath
:
this
.
config
.
isGroupPage
?
this
.
config
.
groupPath
:
this
.
config
.
projectPath
,
first
:
GRAPHQL_PAGE_SIZE
,
};
},
tracking
()
{
tracking
()
{
return
{
return
{
label
:
'
registry_repository_delete
'
,
label
:
'
registry_repository_delete
'
,
};
};
},
},
isLoading
()
{
return
this
.
$apollo
.
queries
.
images
.
loading
||
this
.
mutationLoading
;
},
showCommands
()
{
showCommands
()
{
return
Boolean
(
!
this
.
isLoading
&&
!
this
.
config
?.
isGroupPage
&&
this
.
images
?.
length
);
return
Boolean
(
!
this
.
isLoading
&&
!
this
.
config
?.
isGroupPage
&&
this
.
images
?.
length
);
},
},
...
@@ -93,19 +142,7 @@ export default {
...
@@ -93,19 +142,7 @@ export default {
:
DELETE_IMAGE_ERROR_MESSAGE
;
:
DELETE_IMAGE_ERROR_MESSAGE
;
},
},
},
},
mounted
()
{
this
.
loadImageList
(
this
.
$route
.
name
);
},
methods
:
{
methods
:
{
...
mapActions
([
'
requestImagesList
'
,
'
requestDeleteImage
'
]),
loadImageList
(
fromName
)
{
if
(
!
fromName
||
!
this
.
images
?.
length
)
{
return
this
.
requestImagesList
().
then
(()
=>
{
this
.
isEmpty
=
this
.
images
.
length
===
0
;
});
}
return
Promise
.
resolve
();
},
deleteImage
(
item
)
{
deleteImage
(
item
)
{
this
.
track
(
'
click_button
'
);
this
.
track
(
'
click_button
'
);
this
.
itemToDelete
=
item
;
this
.
itemToDelete
=
item
;
...
@@ -113,18 +150,59 @@ export default {
...
@@ -113,18 +150,59 @@ export default {
},
},
handleDeleteImage
()
{
handleDeleteImage
()
{
this
.
track
(
'
confirm_delete
'
);
this
.
track
(
'
confirm_delete
'
);
return
this
.
requestDeleteImage
(
this
.
itemToDelete
)
this
.
mutationLoading
=
true
;
.
then
(()
=>
{
return
this
.
$apollo
this
.
deleteAlertType
=
'
success
'
;
.
mutate
({
mutation
:
deleteContainerRepository
,
variables
:
{
id
:
this
.
itemToDelete
.
id
,
},
})
.
then
(({
data
})
=>
{
if
(
data
?.
destroyContainerRepository
?.
errors
[
0
])
{
this
.
deleteAlertType
=
'
danger
'
;
}
else
{
this
.
deleteAlertType
=
'
success
'
;
}
})
})
.
catch
(()
=>
{
.
catch
(()
=>
{
this
.
deleteAlertType
=
'
danger
'
;
this
.
deleteAlertType
=
'
danger
'
;
})
.
finally
(()
=>
{
this
.
mutationLoading
=
false
;
});
});
},
},
dismissDeleteAlert
()
{
dismissDeleteAlert
()
{
this
.
deleteAlertType
=
null
;
this
.
deleteAlertType
=
null
;
this
.
itemToDelete
=
{};
this
.
itemToDelete
=
{};
},
},
fetchNextPage
()
{
if
(
this
.
pageInfo
?.
hasNextPage
)
{
this
.
$apollo
.
queries
.
images
.
fetchMore
({
variables
:
{
after
:
this
.
pageInfo
?.
endCursor
,
first
:
GRAPHQL_PAGE_SIZE
,
},
updateQuery
(
previousResult
,
{
fetchMoreResult
})
{
return
fetchMoreResult
;
},
});
}
},
fetchPreviousPage
()
{
if
(
this
.
pageInfo
?.
hasPreviousPage
)
{
this
.
$apollo
.
queries
.
images
.
fetchMore
({
variables
:
{
first
:
null
,
before
:
this
.
pageInfo
?.
startCursor
,
last
:
GRAPHQL_PAGE_SIZE
,
},
updateQuery
(
previousResult
,
{
fetchMoreResult
})
{
return
fetchMoreResult
;
},
});
}
},
},
},
};
};
</
script
>
</
script
>
...
@@ -134,7 +212,7 @@ export default {
...
@@ -134,7 +212,7 @@ export default {
<gl-alert
<gl-alert
v-if=
"showDeleteAlert"
v-if=
"showDeleteAlert"
:variant=
"deleteAlertType"
:variant=
"deleteAlertType"
class=
"
mt-2
"
class=
"
gl-mt-5
"
dismissible
dismissible
@
dismiss=
"dismissDeleteAlert"
@
dismiss=
"dismissDeleteAlert"
>
>
...
@@ -165,7 +243,7 @@ export default {
...
@@ -165,7 +243,7 @@ export default {
<
template
v-else
>
<
template
v-else
>
<registry-header
<registry-header
:images-count=
"
pagination.total
"
:images-count=
"
containerRepositoriesCount
"
:expiration-policy=
"config.expirationPolicy"
:expiration-policy=
"config.expirationPolicy"
:help-page-path=
"config.helpPagePath"
:help-page-path=
"config.helpPagePath"
:expiration-policy-help-page-path=
"config.expirationPolicyHelpPagePath"
:expiration-policy-help-page-path=
"config.expirationPolicyHelpPagePath"
...
@@ -176,7 +254,7 @@ export default {
...
@@ -176,7 +254,7 @@ export default {
</
template
>
</
template
>
</registry-header>
</registry-header>
<div
v-if=
"isLoading"
class=
"
mt-2
"
>
<div
v-if=
"isLoading"
class=
"
gl-mt-5
"
>
<gl-skeleton-loader
<gl-skeleton-loader
v-for=
"index in $options.loader.repeat"
v-for=
"index in $options.loader.repeat"
:key=
"index"
:key=
"index"
...
@@ -190,16 +268,17 @@ export default {
...
@@ -190,16 +268,17 @@ export default {
</gl-skeleton-loader>
</gl-skeleton-loader>
</div>
</div>
<
template
v-else
>
<
template
v-else
>
<template
v-if=
"
!isEmpty
"
>
<template
v-if=
"
images.length > 0 || name
"
>
<div
class=
"gl-display-flex gl-p-1 gl-mt-3"
data-testid=
"listHeader"
>
<div
class=
"gl-display-flex gl-p-1 gl-mt-3"
data-testid=
"listHeader"
>
<div
class=
"gl-flex-fill-1"
>
<div
class=
"gl-flex-fill-1"
>
<h5>
{{
$options
.
i18n
.
IMAGE_REPOSITORY_LIST_LABEL
}}
</h5>
<h5>
{{
$options
.
i18n
.
IMAGE_REPOSITORY_LIST_LABEL
}}
</h5>
</div>
</div>
<div>
<div>
<gl-search-box-by-click
<gl-search-box-by-click
v-model=
"search"
v-model=
"search
Value
"
:placeholder=
"$options.i18n.SEARCH_PLACEHOLDER_TEXT"
:placeholder=
"$options.i18n.SEARCH_PLACEHOLDER_TEXT"
@
submit=
"requestImagesList(
{ name: $event })"
@
clear=
"name = null"
@
submit=
"name = $event"
/>
/>
</div>
</div>
</div>
</div>
...
@@ -207,9 +286,10 @@ export default {
...
@@ -207,9 +286,10 @@ export default {
<image-list
<image-list
v-if=
"images.length"
v-if=
"images.length"
:images=
"images"
:images=
"images"
:pagination=
"pagination"
:page-info=
"pageInfo"
@
pageChange=
"requestImagesList(
{ pagination: { page: $event }, name: search })"
@
delete=
"deleteImage"
@
delete=
"deleteImage"
@
prev-page=
"fetchPreviousPage"
@
next-page=
"fetchNextPage"
/>
/>
<gl-empty-state
<gl-empty-state
...
...
app/views/groups/registry/repositories/index.html.haml
View file @
341637eb
...
@@ -16,4 +16,5 @@
...
@@ -16,4 +16,5 @@
"cleanup_policies_help_page_path"
=>
help_page_path
(
'user/packages/container_registry/index'
,
anchor:
'how-the-cleanup-policy-works'
),
"cleanup_policies_help_page_path"
=>
help_page_path
(
'user/packages/container_registry/index'
,
anchor:
'how-the-cleanup-policy-works'
),
"is_admin"
:
current_user
&
.
admin
.
to_s
,
"is_admin"
:
current_user
&
.
admin
.
to_s
,
is_group_page:
"true"
,
is_group_page:
"true"
,
"group_path"
:
@group
.
full_path
,
character_error:
@character_error
.
to_s
}
}
character_error:
@character_error
.
to_s
}
}
app/views/projects/registry/repositories/index.html.haml
View file @
341637eb
...
@@ -17,6 +17,6 @@
...
@@ -17,6 +17,6 @@
"garbage_collection_help_page_path"
=>
help_page_path
(
'administration/packages/container_registry'
,
anchor:
'container-registry-garbage-collection'
),
"garbage_collection_help_page_path"
=>
help_page_path
(
'administration/packages/container_registry'
,
anchor:
'container-registry-garbage-collection'
),
"run_cleanup_policies_help_page_path"
=>
help_page_path
(
'administration/packages/container_registry'
,
anchor:
'run-the-cleanup-policy-now'
),
"run_cleanup_policies_help_page_path"
=>
help_page_path
(
'administration/packages/container_registry'
,
anchor:
'run-the-cleanup-policy-now'
),
"cleanup_policies_help_page_path"
=>
help_page_path
(
'user/packages/container_registry/index'
,
anchor:
'how-the-cleanup-policy-works'
),
"cleanup_policies_help_page_path"
=>
help_page_path
(
'user/packages/container_registry/index'
,
anchor:
'how-the-cleanup-policy-works'
),
"project_path"
:
@project
.
full_path
,
"is_admin"
:
current_user
&
.
admin
.
to_s
,
"is_admin"
:
current_user
&
.
admin
.
to_s
,
character_error:
@character_error
.
to_s
}
}
character_error:
@character_error
.
to_s
}
}
changelogs/unreleased/276432-refactor-container-registry-frontend-to-graphql.yml
0 → 100644
View file @
341637eb
---
title
:
Refactor container registry list page to grapqhl
merge_request
:
48602
author
:
type
:
changed
spec/features/groups/container_registry_spec.rb
View file @
341637eb
...
@@ -51,13 +51,6 @@ RSpec.describe 'Container Registry', :js do
...
@@ -51,13 +51,6 @@ RSpec.describe 'Container Registry', :js do
expect
(
page
).
to
have_content
'my/image'
expect
(
page
).
to
have_content
'my/image'
end
end
it
'image repository delete is disabled'
do
visit_container_registry
delete_btn
=
find
(
'[title="Remove repository"]'
)
expect
(
delete_btn
).
to
be_disabled
end
it
'navigates to repo details'
do
it
'navigates to repo details'
do
visit_container_registry_details
(
'my/image'
)
visit_container_registry_details
(
'my/image'
)
...
...
spec/features/projects/container_registry_spec.rb
View file @
341637eb
...
@@ -94,7 +94,8 @@ RSpec.describe 'Container Registry', :js do
...
@@ -94,7 +94,8 @@ RSpec.describe 'Container Registry', :js do
end
end
it
(
'pagination navigate to the second page'
)
do
it
(
'pagination navigate to the second page'
)
do
visit_second_page
visit_details_second_page
expect
(
page
).
to
have_content
'20'
expect
(
page
).
to
have_content
'20'
end
end
end
end
...
@@ -116,22 +117,23 @@ RSpec.describe 'Container Registry', :js do
...
@@ -116,22 +117,23 @@ RSpec.describe 'Container Registry', :js do
context
'when there are more than 10 images'
do
context
'when there are more than 10 images'
do
before
do
before
do
create_list
(
:container_repository
,
12
,
project:
project
)
project
.
container_repositories
<<
container_repository
project
.
container_repositories
<<
container_repository
create_list
(
:container_repository
,
12
,
project:
project
)
visit_container_registry
visit_container_registry
end
end
it
'shows pagination'
do
it
'shows pagination'
do
expect
(
page
).
to
have_css
'.gl-pagination'
expect
(
page
).
to
have_css
'.gl-
keyset-
pagination'
end
end
it
'pagination goes to second page'
do
it
'pagination goes to second page'
do
visit_
second
_page
visit_
list_next
_page
expect
(
page
).
to
have_content
'my/image'
expect
(
page
).
to
have_content
'my/image'
end
end
it
'pagination is preserved after navigating back from details'
do
it
'pagination is preserved after navigating back from details'
do
visit_
second
_page
visit_
list_next
_page
click_link
'my/image'
click_link
'my/image'
breadcrumb
=
find
'.breadcrumbs'
breadcrumb
=
find
'.breadcrumbs'
breadcrumb
.
click_link
'Container Registry'
breadcrumb
.
click_link
'Container Registry'
...
@@ -148,7 +150,12 @@ RSpec.describe 'Container Registry', :js do
...
@@ -148,7 +150,12 @@ RSpec.describe 'Container Registry', :js do
click_link
name
click_link
name
end
end
def
visit_second_page
def
visit_list_next_page
pagination
=
find
'.gl-keyset-pagination'
pagination
.
click_button
'Next'
end
def
visit_details_second_page
pagination
=
find
'.gl-pagination'
pagination
=
find
'.gl-pagination'
pagination
.
click_link
'2'
pagination
.
click_link
'2'
end
end
...
...
spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
View file @
341637eb
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlIcon
,
GlSprintf
}
from
'
@gitlab/ui
'
;
import
{
GlIcon
,
GlSprintf
}
from
'
@gitlab/ui
'
;
import
{
createMockDirective
,
getBinding
}
from
'
helpers/vue_mock_directive
'
;
import
{
createMockDirective
,
getBinding
}
from
'
helpers/vue_mock_directive
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
ClipboardButton
from
'
~/vue_shared/components/clipboard_button.vue
'
;
import
ClipboardButton
from
'
~/vue_shared/components/clipboard_button.vue
'
;
import
Component
from
'
~/registry/explorer/components/list_page/image_list_row.vue
'
;
import
Component
from
'
~/registry/explorer/components/list_page/image_list_row.vue
'
;
import
ListItem
from
'
~/vue_shared/components/registry/list_item.vue
'
;
import
ListItem
from
'
~/vue_shared/components/registry/list_item.vue
'
;
...
@@ -11,13 +12,15 @@ import {
...
@@ -11,13 +12,15 @@ import {
REMOVE_REPOSITORY_LABEL
,
REMOVE_REPOSITORY_LABEL
,
ASYNC_DELETE_IMAGE_ERROR_MESSAGE
,
ASYNC_DELETE_IMAGE_ERROR_MESSAGE
,
CLEANUP_TIMED_OUT_ERROR_MESSAGE
,
CLEANUP_TIMED_OUT_ERROR_MESSAGE
,
IMAGE_DELETE_SCHEDULED_STATUS
,
IMAGE_FAILED_DELETED_STATUS
,
}
from
'
~/registry/explorer/constants
'
;
}
from
'
~/registry/explorer/constants
'
;
import
{
RouterLink
}
from
'
../../stubs
'
;
import
{
RouterLink
}
from
'
../../stubs
'
;
import
{
imagesListResponse
}
from
'
../../mock_data
'
;
import
{
imagesListResponse
}
from
'
../../mock_data
'
;
describe
(
'
Image List Row
'
,
()
=>
{
describe
(
'
Image List Row
'
,
()
=>
{
let
wrapper
;
let
wrapper
;
const
item
=
imagesListResponse
.
data
[
0
]
;
const
[
item
]
=
imagesListResponse
;
const
findDetailsLink
=
()
=>
wrapper
.
find
(
'
[data-testid="details-link"]
'
);
const
findDetailsLink
=
()
=>
wrapper
.
find
(
'
[data-testid="details-link"]
'
);
const
findTagsCount
=
()
=>
wrapper
.
find
(
'
[data-testid="tagsCount"]
'
);
const
findTagsCount
=
()
=>
wrapper
.
find
(
'
[data-testid="tagsCount"]
'
);
...
@@ -50,13 +53,15 @@ describe('Image List Row', () => {
...
@@ -50,13 +53,15 @@ describe('Image List Row', () => {
describe
(
'
main tooltip
'
,
()
=>
{
describe
(
'
main tooltip
'
,
()
=>
{
it
(
`the title is
${
ROW_SCHEDULED_FOR_DELETION
}
`
,
()
=>
{
it
(
`the title is
${
ROW_SCHEDULED_FOR_DELETION
}
`
,
()
=>
{
mountComponent
();
mountComponent
();
const
tooltip
=
getBinding
(
wrapper
.
element
,
'
gl-tooltip
'
);
const
tooltip
=
getBinding
(
wrapper
.
element
,
'
gl-tooltip
'
);
expect
(
tooltip
).
toBeDefined
();
expect
(
tooltip
).
toBeDefined
();
expect
(
tooltip
.
value
.
title
).
toBe
(
ROW_SCHEDULED_FOR_DELETION
);
expect
(
tooltip
.
value
.
title
).
toBe
(
ROW_SCHEDULED_FOR_DELETION
);
});
});
it
(
'
is disabled when item is being deleted
'
,
()
=>
{
it
(
'
is disabled when item is being deleted
'
,
()
=>
{
mountComponent
({
item
:
{
...
item
,
deleting
:
true
}
});
mountComponent
({
item
:
{
...
item
,
status
:
IMAGE_DELETE_SCHEDULED_STATUS
}
});
const
tooltip
=
getBinding
(
wrapper
.
element
,
'
gl-tooltip
'
);
const
tooltip
=
getBinding
(
wrapper
.
element
,
'
gl-tooltip
'
);
expect
(
tooltip
.
value
.
disabled
).
toBe
(
false
);
expect
(
tooltip
.
value
.
disabled
).
toBe
(
false
);
});
});
...
@@ -65,12 +70,13 @@ describe('Image List Row', () => {
...
@@ -65,12 +70,13 @@ describe('Image List Row', () => {
describe
(
'
image title and path
'
,
()
=>
{
describe
(
'
image title and path
'
,
()
=>
{
it
(
'
contains a link to the details page
'
,
()
=>
{
it
(
'
contains a link to the details page
'
,
()
=>
{
mountComponent
();
mountComponent
();
const
link
=
findDetailsLink
();
const
link
=
findDetailsLink
();
expect
(
link
.
html
()).
toContain
(
item
.
path
);
expect
(
link
.
html
()).
toContain
(
item
.
path
);
expect
(
link
.
props
(
'
to
'
)).
toMatchObject
({
expect
(
link
.
props
(
'
to
'
)).
toMatchObject
({
name
:
'
details
'
,
name
:
'
details
'
,
params
:
{
params
:
{
id
:
item
.
id
,
id
:
getIdFromGraphQLId
(
item
.
id
)
,
},
},
});
});
});
});
...
@@ -85,16 +91,18 @@ describe('Image List Row', () => {
...
@@ -85,16 +91,18 @@ describe('Image List Row', () => {
describe
(
'
warning icon
'
,
()
=>
{
describe
(
'
warning icon
'
,
()
=>
{
it
.
each
`
it
.
each
`
failedDelete | cleanup_policy_started_a
t | shown | title
status | expirationPolicyStartedA
t | shown | title
${
true
}
|
${
true
}
|
${
true
}
|
${
ASYNC_DELETE_IMAGE_ERROR_MESSAGE
}
${
IMAGE_FAILED_DELETED_STATUS
}
|
${
true
}
|
${
true
}
|
${
ASYNC_DELETE_IMAGE_ERROR_MESSAGE
}
${
false
}
|
${
true
}
|
${
true
}
|
${
CLEANUP_TIMED_OUT_ERROR_MESSAGE
}
${
''
}
|
${
true
}
|
${
true
}
|
${
CLEANUP_TIMED_OUT_ERROR_MESSAGE
}
${
false
}
|
${
false
}
|
${
false
}
|
${
''
}
${
''
}
|
${
false
}
|
${
false
}
|
${
''
}
`
(
`
(
'
when failedDelete is $failedDelete and cleanup_policy_started_at is $cleanup_policy_started_at
'
,
'
when status is $status and expirationPolicyStartedAt is $expirationPolicyStartedAt
'
,
({
cleanup_policy_started_at
,
failedDelete
,
shown
,
title
})
=>
{
({
expirationPolicyStartedAt
,
status
,
shown
,
title
})
=>
{
mountComponent
({
item
:
{
...
item
,
failedDelete
,
cleanup_policy_started_at
}
});
mountComponent
({
item
:
{
...
item
,
status
,
expirationPolicyStartedAt
}
});
const
icon
=
findWarningIcon
();
const
icon
=
findWarningIcon
();
expect
(
icon
.
exists
()).
toBe
(
shown
);
expect
(
icon
.
exists
()).
toBe
(
shown
);
if
(
shown
)
{
if
(
shown
)
{
const
tooltip
=
getBinding
(
icon
.
element
,
'
gl-tooltip
'
);
const
tooltip
=
getBinding
(
icon
.
element
,
'
gl-tooltip
'
);
expect
(
tooltip
.
value
.
title
).
toBe
(
title
);
expect
(
tooltip
.
value
.
title
).
toBe
(
title
);
...
@@ -112,30 +120,33 @@ describe('Image List Row', () => {
...
@@ -112,30 +120,33 @@ describe('Image List Row', () => {
it
(
'
has the correct props
'
,
()
=>
{
it
(
'
has the correct props
'
,
()
=>
{
mountComponent
();
mountComponent
();
expect
(
findDeleteBtn
().
attributes
()).
toMatchObject
({
expect
(
findDeleteBtn
().
props
()).
toMatchObject
({
title
:
REMOVE_REPOSITORY_LABEL
,
title
:
REMOVE_REPOSITORY_LABEL
,
tooltip
disabled
:
`
${
Boolean
(
item
.
destroy_path
)}
`
,
tooltip
Disabled
:
item
.
canDelete
,
tooltip
t
itle
:
LIST_DELETE_BUTTON_DISABLED
,
tooltip
T
itle
:
LIST_DELETE_BUTTON_DISABLED
,
});
});
});
});
it
(
'
emits a delete event
'
,
()
=>
{
it
(
'
emits a delete event
'
,
()
=>
{
mountComponent
();
mountComponent
();
findDeleteBtn
().
vm
.
$emit
(
'
delete
'
);
findDeleteBtn
().
vm
.
$emit
(
'
delete
'
);
expect
(
wrapper
.
emitted
(
'
delete
'
)).
toEqual
([[
item
]]);
expect
(
wrapper
.
emitted
(
'
delete
'
)).
toEqual
([[
item
]]);
});
});
it
.
each
`
it
.
each
`
destroy_path | deleting
| state
canDelete | status
| state
${
null
}
|
${
null
}
|
${
'
true
'
}
${
false
}
|
${
''
}
|
${
true
}
${
null
}
|
${
true
}
|
${
'
true
'
}
${
false
}
|
${
IMAGE_DELETE_SCHEDULED_STATUS
}
|
${
true
}
${
'
foo
'
}
|
${
true
}
|
${
'
true
'
}
${
true
}
|
${
IMAGE_DELETE_SCHEDULED_STATUS
}
|
${
true
}
${
'
foo
'
}
|
${
false
}
|
${
undefined
}
${
true
}
|
${
''
}
|
${
false
}
`
(
`
(
'
disabled is $state when destroy_path is $destroy_path and deleting is $deleting
'
,
'
disabled is $state when canDelete is $canDelete and status is $status
'
,
({
destroy_path
,
deleting
,
state
})
=>
{
({
canDelete
,
status
,
state
})
=>
{
mountComponent
({
item
:
{
...
item
,
destroy_path
,
deleting
}
});
mountComponent
({
item
:
{
...
item
,
canDelete
,
status
}
});
expect
(
findDeleteBtn
().
attributes
(
'
disabled
'
)).
toBe
(
state
);
expect
(
findDeleteBtn
().
props
(
'
disabled
'
)).
toBe
(
state
);
},
},
);
);
});
});
...
@@ -155,11 +166,13 @@ describe('Image List Row', () => {
...
@@ -155,11 +166,13 @@ describe('Image List Row', () => {
describe
(
'
tags count text
'
,
()
=>
{
describe
(
'
tags count text
'
,
()
=>
{
it
(
'
with one tag in the image
'
,
()
=>
{
it
(
'
with one tag in the image
'
,
()
=>
{
mountComponent
({
item
:
{
...
item
,
tags_count
:
1
}
});
mountComponent
({
item
:
{
...
item
,
tagsCount
:
1
}
});
expect
(
findTagsCount
().
text
()).
toMatchInterpolatedText
(
'
1 Tag
'
);
expect
(
findTagsCount
().
text
()).
toMatchInterpolatedText
(
'
1 Tag
'
);
});
});
it
(
'
with more than one tag in the image
'
,
()
=>
{
it
(
'
with more than one tag in the image
'
,
()
=>
{
mountComponent
({
item
:
{
...
item
,
tags_count
:
3
}
});
mountComponent
({
item
:
{
...
item
,
tagsCount
:
3
}
});
expect
(
findTagsCount
().
text
()).
toMatchInterpolatedText
(
'
3 Tags
'
);
expect
(
findTagsCount
().
text
()).
toMatchInterpolatedText
(
'
3 Tags
'
);
});
});
});
});
...
...
spec/frontend/registry/explorer/components/list_page/image_list_spec.js
View file @
341637eb
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlPagination
}
from
'
@gitlab/ui
'
;
import
{
Gl
Keyset
Pagination
}
from
'
@gitlab/ui
'
;
import
Component
from
'
~/registry/explorer/components/list_page/image_list.vue
'
;
import
Component
from
'
~/registry/explorer/components/list_page/image_list.vue
'
;
import
ImageListRow
from
'
~/registry/explorer/components/list_page/image_list_row.vue
'
;
import
ImageListRow
from
'
~/registry/explorer/components/list_page/image_list_row.vue
'
;
import
{
imagesListResponse
,
imagePagination
}
from
'
../../mock_data
'
;
import
{
imagesListResponse
,
pageInfo
as
defaultPageInfo
}
from
'
../../mock_data
'
;
describe
(
'
Image List
'
,
()
=>
{
describe
(
'
Image List
'
,
()
=>
{
let
wrapper
;
let
wrapper
;
const
findRow
=
()
=>
wrapper
.
findAll
(
ImageListRow
);
const
findRow
=
()
=>
wrapper
.
findAll
(
ImageListRow
);
const
findPagination
=
()
=>
wrapper
.
find
(
GlPagination
);
const
findPagination
=
()
=>
wrapper
.
find
(
Gl
Keyset
Pagination
);
const
mountComponent
=
()
=>
{
const
mountComponent
=
(
pageInfo
=
defaultPageInfo
)
=>
{
wrapper
=
shallowMount
(
Component
,
{
wrapper
=
shallowMount
(
Component
,
{
propsData
:
{
propsData
:
{
images
:
imagesListResponse
.
data
,
images
:
imagesListResponse
,
pag
ination
:
imagePagination
,
pag
eInfo
,
},
},
});
});
};
};
beforeEach
(()
=>
{
mountComponent
();
});
afterEach
(()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
.
destroy
();
wrapper
=
null
;
wrapper
=
null
;
...
@@ -31,10 +27,14 @@ describe('Image List', () => {
...
@@ -31,10 +27,14 @@ describe('Image List', () => {
describe
(
'
list
'
,
()
=>
{
describe
(
'
list
'
,
()
=>
{
it
(
'
contains one list element for each image
'
,
()
=>
{
it
(
'
contains one list element for each image
'
,
()
=>
{
expect
(
findRow
().
length
).
toBe
(
imagesListResponse
.
data
.
length
);
mountComponent
();
expect
(
findRow
().
length
).
toBe
(
imagesListResponse
.
length
);
});
});
it
(
'
when delete event is emitted on the row it emits up a delete event
'
,
()
=>
{
it
(
'
when delete event is emitted on the row it emits up a delete event
'
,
()
=>
{
mountComponent
();
findRow
()
findRow
()
.
at
(
0
)
.
at
(
0
)
.
vm
.
$emit
(
'
delete
'
,
'
foo
'
);
.
vm
.
$emit
(
'
delete
'
,
'
foo
'
);
...
@@ -44,19 +44,41 @@ describe('Image List', () => {
...
@@ -44,19 +44,41 @@ describe('Image List', () => {
describe
(
'
pagination
'
,
()
=>
{
describe
(
'
pagination
'
,
()
=>
{
it
(
'
exists
'
,
()
=>
{
it
(
'
exists
'
,
()
=>
{
mountComponent
();
expect
(
findPagination
().
exists
()).
toBe
(
true
);
expect
(
findPagination
().
exists
()).
toBe
(
true
);
});
});
it
(
'
is wired to the correct pagination props
'
,
()
=>
{
it
.
each
`
const
pagination
=
findPagination
();
hasNextPage | hasPreviousPage | isVisible
expect
(
pagination
.
props
(
'
perPage
'
)).
toBe
(
imagePagination
.
perPage
);
${
true
}
|
${
true
}
|
${
true
}
expect
(
pagination
.
props
(
'
totalItems
'
)).
toBe
(
imagePagination
.
total
);
${
true
}
|
${
false
}
|
${
true
}
expect
(
pagination
.
props
(
'
value
'
)).
toBe
(
imagePagination
.
page
);
${
false
}
|
${
true
}
|
${
true
}
`
(
'
when hasNextPage is $hasNextPage and hasPreviousPage is $hasPreviousPage: is $isVisible that the component is visible
'
,
({
hasNextPage
,
hasPreviousPage
,
isVisible
})
=>
{
mountComponent
({
hasNextPage
,
hasPreviousPage
});
expect
(
findPagination
().
exists
()).
toBe
(
isVisible
);
expect
(
findPagination
().
props
(
'
hasPreviousPage
'
)).
toBe
(
hasPreviousPage
);
expect
(
findPagination
().
props
(
'
hasNextPage
'
)).
toBe
(
hasNextPage
);
},
);
it
(
'
emits "prev-page" when the user clicks the back page button
'
,
()
=>
{
mountComponent
({
hasPreviousPage
:
true
});
findPagination
().
vm
.
$emit
(
'
prev
'
);
expect
(
wrapper
.
emitted
(
'
prev-page
'
)).
toEqual
([[]]);
});
});
it
(
'
emits a pageChange event when the page change
'
,
()
=>
{
it
(
'
emits "next-page" when the user clicks the forward page button
'
,
()
=>
{
findPagination
().
vm
.
$emit
(
GlPagination
.
model
.
event
,
2
);
mountComponent
({
hasNextPage
:
true
});
expect
(
wrapper
.
emitted
(
'
pageChange
'
)).
toEqual
([[
2
]]);
findPagination
().
vm
.
$emit
(
'
next
'
);
expect
(
wrapper
.
emitted
(
'
next-page
'
)).
toEqual
([[]]);
});
});
});
});
});
});
spec/frontend/registry/explorer/mock_data.js
View file @
341637eb
...
@@ -45,21 +45,32 @@ export const registryServerResponse = [
...
@@ -45,21 +45,32 @@ export const registryServerResponse = [
},
},
];
];
export
const
imagesListResponse
=
{
export
const
imagesListResponse
=
[
data
:
[
{
{
__typename
:
'
ContainerRepository
'
,
path
:
'
foo
'
,
id
:
'
gid://gitlab/ContainerRepository/26
'
,
location
:
'
location
'
,
name
:
'
rails-12009
'
,
destroy_path
:
'
path
'
,
path
:
'
gitlab-org/gitlab-test/rails-12009
'
,
},
status
:
null
,
{
location
:
'
0.0.0.0:5000/gitlab-org/gitlab-test/rails-12009
'
,
path
:
'
bar
'
,
canDelete
:
true
,
location
:
'
location-2
'
,
createdAt
:
'
2020-11-03T13:29:21Z
'
,
destroy_path
:
'
path-2
'
,
tagsCount
:
18
,
},
expirationPolicyStartedAt
:
null
,
],
},
headers
,
{
};
__typename
:
'
ContainerRepository
'
,
id
:
'
gid://gitlab/ContainerRepository/11
'
,
name
:
'
rails-20572
'
,
path
:
'
gitlab-org/gitlab-test/rails-20572
'
,
status
:
null
,
location
:
'
0.0.0.0:5000/gitlab-org/gitlab-test/rails-20572
'
,
canDelete
:
true
,
createdAt
:
'
2020-09-21T06:57:43Z
'
,
tagsCount
:
1
,
expirationPolicyStartedAt
:
null
,
},
];
export
const
tagsListResponse
=
{
export
const
tagsListResponse
=
{
data
:
[
data
:
[
...
@@ -90,12 +101,12 @@ export const tagsListResponse = {
...
@@ -90,12 +101,12 @@ export const tagsListResponse = {
headers
,
headers
,
};
};
export
const
imagePagination
=
{
export
const
pageInfo
=
{
perPage
:
10
,
hasNextPage
:
true
,
page
:
1
,
hasPreviousPage
:
true
,
total
:
14
,
startCursor
:
'
eyJpZCI6IjI2In0
'
,
totalPages
:
2
,
endCursor
:
'
eyJpZCI6IjgifQ
'
,
nextPage
:
2
,
__typename
:
'
ContainerRepositoryConnection
'
,
};
};
export
const
imageDetailsMock
=
{
export
const
imageDetailsMock
=
{
...
@@ -108,3 +119,76 @@ export const imageDetailsMock = {
...
@@ -108,3 +119,76 @@ export const imageDetailsMock = {
cleanup_policy_started_at
:
null
,
cleanup_policy_started_at
:
null
,
delete_api_path
:
'
http://0.0.0.0:3000/api/v4/projects/1/registry/repositories/1
'
,
delete_api_path
:
'
http://0.0.0.0:3000/api/v4/projects/1/registry/repositories/1
'
,
};
};
export
const
graphQLImageListMock
=
{
data
:
{
project
:
{
__typename
:
'
Project
'
,
containerRepositoriesCount
:
2
,
containerRepositories
:
{
__typename
:
'
ContainerRepositoryConnection
'
,
nodes
:
imagesListResponse
,
pageInfo
,
},
},
},
};
export
const
graphQLEmptyImageListMock
=
{
data
:
{
project
:
{
__typename
:
'
Project
'
,
containerRepositoriesCount
:
2
,
containerRepositories
:
{
__typename
:
'
ContainerRepositoryConnection
'
,
nodes
:
[],
pageInfo
,
},
},
},
};
export
const
graphQLEmptyGroupImageListMock
=
{
data
:
{
group
:
{
__typename
:
'
Group
'
,
containerRepositoriesCount
:
2
,
containerRepositories
:
{
__typename
:
'
ContainerRepositoryConnection
'
,
nodes
:
[],
pageInfo
,
},
},
},
};
export
const
deletedContainerRepository
=
{
id
:
'
gid://gitlab/ContainerRepository/11
'
,
status
:
'
DELETE_SCHEDULED
'
,
path
:
'
gitlab-org/gitlab-test/rails-12009
'
,
__typename
:
'
ContainerRepository
'
,
};
export
const
graphQLImageDeleteMock
=
{
data
:
{
destroyContainerRepository
:
{
containerRepository
:
{
...
deletedContainerRepository
,
},
errors
:
[],
__typename
:
'
DestroyContainerRepositoryPayload
'
,
},
},
};
export
const
graphQLImageDeleteMockError
=
{
data
:
{
destroyContainerRepository
:
{
containerRepository
:
{
...
deletedContainerRepository
,
},
errors
:
[
'
foo
'
],
__typename
:
'
DestroyContainerRepositoryPayload
'
,
},
},
};
spec/frontend/registry/explorer/pages/list_spec.js
View file @
341637eb
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
{
GlSkeletonLoader
,
GlSprintf
,
GlAlert
,
GlSearchBoxByClick
}
from
'
@gitlab/ui
'
;
import
{
GlSkeletonLoader
,
GlSprintf
,
GlAlert
,
GlSearchBoxByClick
}
from
'
@gitlab/ui
'
;
import
createMockApollo
from
'
jest/helpers/mock_apollo_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
Tracking
from
'
~/tracking
'
;
import
Tracking
from
'
~/tracking
'
;
import
component
from
'
~/registry/explorer/pages/list.vue
'
;
import
component
from
'
~/registry/explorer/pages/list.vue
'
;
...
@@ -10,26 +12,36 @@ import RegistryHeader from '~/registry/explorer/components/list_page/registry_he
...
@@ -10,26 +12,36 @@ import RegistryHeader from '~/registry/explorer/components/list_page/registry_he
import
ImageList
from
'
~/registry/explorer/components/list_page/image_list.vue
'
;
import
ImageList
from
'
~/registry/explorer/components/list_page/image_list.vue
'
;
import
TitleArea
from
'
~/vue_shared/components/registry/title_area.vue
'
;
import
TitleArea
from
'
~/vue_shared/components/registry/title_area.vue
'
;
import
{
createStore
}
from
'
~/registry/explorer/stores/
'
;
import
{
createStore
}
from
'
~/registry/explorer/stores/
'
;
import
{
import
{
SET_INITIAL_STATE
}
from
'
~/registry/explorer/stores/mutation_types
'
;
SET_MAIN_LOADING
,
SET_IMAGES_LIST_SUCCESS
,
SET_PAGINATION
,
SET_INITIAL_STATE
,
}
from
'
~/registry/explorer/stores/mutation_types
'
;
import
{
import
{
DELETE_IMAGE_SUCCESS_MESSAGE
,
DELETE_IMAGE_SUCCESS_MESSAGE
,
DELETE_IMAGE_ERROR_MESSAGE
,
DELETE_IMAGE_ERROR_MESSAGE
,
IMAGE_REPOSITORY_LIST_LABEL
,
IMAGE_REPOSITORY_LIST_LABEL
,
SEARCH_PLACEHOLDER_TEXT
,
SEARCH_PLACEHOLDER_TEXT
,
}
from
'
~/registry/explorer/constants
'
;
}
from
'
~/registry/explorer/constants
'
;
import
{
imagesListResponse
}
from
'
../mock_data
'
;
import
getProjectContainerRepositories
from
'
~/registry/explorer/graphql/queries/get_project_container_repositories.graphql
'
;
import
getGroupContainerRepositories
from
'
~/registry/explorer/graphql/queries/get_group_container_repositories.graphql
'
;
import
deleteContainerRepository
from
'
~/registry/explorer/graphql/mutations/delete_container_repository.graphql
'
;
import
{
graphQLImageListMock
,
graphQLImageDeleteMock
,
deletedContainerRepository
,
graphQLImageDeleteMockError
,
graphQLEmptyImageListMock
,
graphQLEmptyGroupImageListMock
,
pageInfo
,
}
from
'
../mock_data
'
;
import
{
GlModal
,
GlEmptyState
}
from
'
../stubs
'
;
import
{
GlModal
,
GlEmptyState
}
from
'
../stubs
'
;
import
{
$toast
}
from
'
../../shared/mocks
'
;
import
{
$toast
}
from
'
../../shared/mocks
'
;
const
localVue
=
createLocalVue
();
describe
(
'
List Page
'
,
()
=>
{
describe
(
'
List Page
'
,
()
=>
{
let
wrapper
;
let
wrapper
;
let
dispatchSpy
;
let
store
;
let
store
;
let
apolloProvider
;
const
findDeleteModal
=
()
=>
wrapper
.
find
(
GlModal
);
const
findDeleteModal
=
()
=>
wrapper
.
find
(
GlModal
);
const
findSkeletonLoader
=
()
=>
wrapper
.
find
(
GlSkeletonLoader
);
const
findSkeletonLoader
=
()
=>
wrapper
.
find
(
GlSkeletonLoader
);
...
@@ -47,8 +59,30 @@ describe('List Page', () => {
...
@@ -47,8 +59,30 @@ describe('List Page', () => {
const
findSearchBox
=
()
=>
wrapper
.
find
(
GlSearchBoxByClick
);
const
findSearchBox
=
()
=>
wrapper
.
find
(
GlSearchBoxByClick
);
const
findEmptySearchMessage
=
()
=>
wrapper
.
find
(
'
[data-testid="emptySearch"]
'
);
const
findEmptySearchMessage
=
()
=>
wrapper
.
find
(
'
[data-testid="emptySearch"]
'
);
const
mountComponent
=
({
mocks
}
=
{})
=>
{
const
waitForApolloRequestRender
=
async
()
=>
{
await
waitForPromises
();
await
wrapper
.
vm
.
$nextTick
();
};
const
mountComponent
=
({
mocks
,
resolver
=
jest
.
fn
().
mockResolvedValue
(
graphQLImageListMock
),
groupResolver
=
jest
.
fn
().
mockResolvedValue
(
graphQLImageListMock
),
mutationResolver
=
jest
.
fn
().
mockResolvedValue
(
graphQLImageDeleteMock
),
}
=
{})
=>
{
localVue
.
use
(
VueApollo
);
const
requestHandlers
=
[
[
getProjectContainerRepositories
,
resolver
],
[
getGroupContainerRepositories
,
groupResolver
],
[
deleteContainerRepository
,
mutationResolver
],
];
apolloProvider
=
createMockApollo
(
requestHandlers
);
wrapper
=
shallowMount
(
component
,
{
wrapper
=
shallowMount
(
component
,
{
localVue
,
apolloProvider
,
store
,
store
,
stubs
:
{
stubs
:
{
GlModal
,
GlModal
,
...
@@ -69,37 +103,21 @@ describe('List Page', () => {
...
@@ -69,37 +103,21 @@ describe('List Page', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
store
=
createStore
();
store
=
createStore
();
dispatchSpy
=
jest
.
spyOn
(
store
,
'
dispatch
'
);
dispatchSpy
.
mockResolvedValue
();
store
.
commit
(
SET_IMAGES_LIST_SUCCESS
,
imagesListResponse
.
data
);
store
.
commit
(
SET_PAGINATION
,
imagesListResponse
.
headers
);
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
.
destroy
();
});
});
describe
(
'
API calls
'
,
()
=>
{
it
(
'
contains registry header
'
,
async
()
=>
{
it
.
each
`
imageList | name | called
${[]}
|
${
'
foo
'
}
|
${[
'
requestImagesList
'
]}
${
imagesListResponse
.
data
}
|
${
undefined
}
|
${[
'
requestImagesList
'
]}
${
imagesListResponse
.
data
}
|
${
'
foo
'
}
|
${
undefined
}
`
(
'
with images equal $imageList and name $name dispatch calls $called
'
,
({
imageList
,
name
,
called
})
=>
{
store
.
commit
(
SET_IMAGES_LIST_SUCCESS
,
imageList
);
dispatchSpy
.
mockClear
();
mountComponent
({
mocks
:
{
$route
:
{
name
}
}
});
expect
(
dispatchSpy
.
mock
.
calls
[
0
]).
toEqual
(
called
);
},
);
});
it
(
'
contains registry header
'
,
()
=>
{
mountComponent
();
mountComponent
();
await
waitForApolloRequestRender
();
expect
(
findRegistryHeader
().
exists
()).
toBe
(
true
);
expect
(
findRegistryHeader
().
exists
()).
toBe
(
true
);
expect
(
findRegistryHeader
().
props
()).
toMatchObject
({
imagesCount
:
2
,
});
});
});
describe
(
'
connection error
'
,
()
=>
{
describe
(
'
connection error
'
,
()
=>
{
...
@@ -111,7 +129,6 @@ describe('List Page', () => {
...
@@ -111,7 +129,6 @@ describe('List Page', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
store
.
commit
(
SET_INITIAL_STATE
,
config
);
store
.
commit
(
SET_INITIAL_STATE
,
config
);
mountComponent
();
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
...
@@ -119,78 +136,103 @@ describe('List Page', () => {
...
@@ -119,78 +136,103 @@ describe('List Page', () => {
});
});
it
(
'
should show an empty state
'
,
()
=>
{
it
(
'
should show an empty state
'
,
()
=>
{
mountComponent
();
expect
(
findEmptyState
().
exists
()).
toBe
(
true
);
expect
(
findEmptyState
().
exists
()).
toBe
(
true
);
});
});
it
(
'
empty state should have an svg-path
'
,
()
=>
{
it
(
'
empty state should have an svg-path
'
,
()
=>
{
mountComponent
();
expect
(
findEmptyState
().
attributes
(
'
svg-path
'
)).
toBe
(
config
.
containersErrorImage
);
expect
(
findEmptyState
().
attributes
(
'
svg-path
'
)).
toBe
(
config
.
containersErrorImage
);
});
});
it
(
'
empty state should have a description
'
,
()
=>
{
it
(
'
empty state should have a description
'
,
()
=>
{
mountComponent
();
expect
(
findEmptyState
().
html
()).
toContain
(
'
connection error
'
);
expect
(
findEmptyState
().
html
()).
toContain
(
'
connection error
'
);
});
});
it
(
'
should not show the loading or default state
'
,
()
=>
{
it
(
'
should not show the loading or default state
'
,
()
=>
{
mountComponent
();
expect
(
findSkeletonLoader
().
exists
()).
toBe
(
false
);
expect
(
findSkeletonLoader
().
exists
()).
toBe
(
false
);
expect
(
findImageList
().
exists
()).
toBe
(
false
);
expect
(
findImageList
().
exists
()).
toBe
(
false
);
});
});
});
});
describe
(
'
isLoading is true
'
,
()
=>
{
describe
(
'
isLoading is true
'
,
()
=>
{
beforeEach
(()
=>
{
it
(
'
shows the skeleton loader
'
,
()
=>
{
store
.
commit
(
SET_MAIN_LOADING
,
true
);
mountComponent
();
mountComponent
();
});
afterEach
(()
=>
store
.
commit
(
SET_MAIN_LOADING
,
false
));
it
(
'
shows the skeleton loader
'
,
()
=>
{
expect
(
findSkeletonLoader
().
exists
()).
toBe
(
true
);
expect
(
findSkeletonLoader
().
exists
()).
toBe
(
true
);
});
});
it
(
'
imagesList is not visible
'
,
()
=>
{
it
(
'
imagesList is not visible
'
,
()
=>
{
mountComponent
();
expect
(
findImageList
().
exists
()).
toBe
(
false
);
expect
(
findImageList
().
exists
()).
toBe
(
false
);
});
});
it
(
'
cli commands is not visible
'
,
()
=>
{
it
(
'
cli commands is not visible
'
,
()
=>
{
mountComponent
();
expect
(
findCliCommands
().
exists
()).
toBe
(
false
);
expect
(
findCliCommands
().
exists
()).
toBe
(
false
);
});
});
});
});
describe
(
'
list is empty
'
,
()
=>
{
describe
(
'
list is empty
'
,
()
=>
{
beforeEach
(()
=>
{
describe
(
'
project page
'
,
()
=>
{
store
.
commit
(
SET_IMAGES_LIST_SUCCESS
,
[]);
const
resolver
=
jest
.
fn
().
mockResolvedValue
(
graphQLEmptyImageListMock
);
mountComponent
();
return
waitForPromises
();
});
it
(
'
cli commands is not visible
'
,
()
=>
{
it
(
'
cli commands is not visible
'
,
async
()
=>
{
expect
(
findCliCommands
().
exists
()).
toBe
(
false
);
mountComponent
({
resolver
});
});
await
waitForApolloRequestRender
();
expect
(
findCliCommands
().
exists
()).
toBe
(
false
);
});
it
(
'
project empty state is visible
'
,
async
()
=>
{
mountComponent
({
resolver
});
await
waitForApolloRequestRender
();
it
(
'
project empty state is visible
'
,
()
=>
{
expect
(
findProjectEmptyState
().
exists
()).
toBe
(
true
);
expect
(
findProjectEmptyState
().
exists
()).
toBe
(
true
);
}
);
});
});
describe
(
'
group page
'
,
()
=>
{
const
groupResolver
=
jest
.
fn
().
mockResolvedValue
(
graphQLEmptyGroupImageListMock
);
describe
(
'
is group page is true
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
store
.
commit
(
SET_INITIAL_STATE
,
{
isGroupPage
:
true
});
store
.
commit
(
SET_INITIAL_STATE
,
{
isGroupPage
:
true
});
mountComponent
();
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
store
.
commit
(
SET_INITIAL_STATE
,
{
isGroupPage
:
undefined
});
store
.
commit
(
SET_INITIAL_STATE
,
{
isGroupPage
:
undefined
});
});
});
it
(
'
group empty state is visible
'
,
()
=>
{
it
(
'
group empty state is visible
'
,
async
()
=>
{
mountComponent
({
groupResolver
});
await
waitForApolloRequestRender
();
expect
(
findGroupEmptyState
().
exists
()).
toBe
(
true
);
expect
(
findGroupEmptyState
().
exists
()).
toBe
(
true
);
});
});
it
(
'
cli commands is not visible
'
,
()
=>
{
it
(
'
cli commands is not visible
'
,
async
()
=>
{
mountComponent
({
groupResolver
});
await
waitForApolloRequestRender
();
expect
(
findCliCommands
().
exists
()).
toBe
(
false
);
expect
(
findCliCommands
().
exists
()).
toBe
(
false
);
});
});
it
(
'
list header is not visible
'
,
()
=>
{
it
(
'
list header is not visible
'
,
async
()
=>
{
mountComponent
({
groupResolver
});
await
waitForApolloRequestRender
();
expect
(
findListHeader
().
exists
()).
toBe
(
false
);
expect
(
findListHeader
().
exists
()).
toBe
(
false
);
});
});
});
});
...
@@ -198,55 +240,91 @@ describe('List Page', () => {
...
@@ -198,55 +240,91 @@ describe('List Page', () => {
describe
(
'
list is not empty
'
,
()
=>
{
describe
(
'
list is not empty
'
,
()
=>
{
describe
(
'
unfiltered state
'
,
()
=>
{
describe
(
'
unfiltered state
'
,
()
=>
{
beforeEach
(
()
=>
{
it
(
'
quick start is visible
'
,
async
()
=>
{
mountComponent
();
mountComponent
();
});
it
(
'
quick start is visible
'
,
()
=>
{
await
waitForApolloRequestRender
();
expect
(
findCliCommands
().
exists
()).
toBe
(
true
);
expect
(
findCliCommands
().
exists
()).
toBe
(
true
);
});
});
it
(
'
list component is visible
'
,
()
=>
{
it
(
'
list component is visible
'
,
async
()
=>
{
mountComponent
();
await
waitForApolloRequestRender
();
expect
(
findImageList
().
exists
()).
toBe
(
true
);
expect
(
findImageList
().
exists
()).
toBe
(
true
);
});
});
it
(
'
list header is visible
'
,
()
=>
{
it
(
'
list header is visible
'
,
async
()
=>
{
mountComponent
();
await
waitForApolloRequestRender
();
const
header
=
findListHeader
();
const
header
=
findListHeader
();
expect
(
header
.
exists
()).
toBe
(
true
);
expect
(
header
.
exists
()).
toBe
(
true
);
expect
(
header
.
text
()).
toBe
(
IMAGE_REPOSITORY_LIST_LABEL
);
expect
(
header
.
text
()).
toBe
(
IMAGE_REPOSITORY_LIST_LABEL
);
});
});
describe
(
'
delete image
'
,
()
=>
{
describe
(
'
delete image
'
,
()
=>
{
const
itemToDelete
=
{
path
:
'
bar
'
};
const
deleteImage
=
async
()
=>
{
it
(
'
should call deleteItem when confirming deletion
'
,
()
=>
{
await
wrapper
.
vm
.
$nextTick
();
dispatchSpy
.
mockResolvedValue
();
findImageList
().
vm
.
$emit
(
'
delete
'
,
itemToDelete
);
findImageList
().
vm
.
$emit
(
'
delete
'
,
deletedContainerRepository
);
expect
(
wrapper
.
vm
.
itemToDelete
).
toEqual
(
itemToDelete
);
findDeleteModal
().
vm
.
$emit
(
'
ok
'
);
findDeleteModal
().
vm
.
$emit
(
'
ok
'
);
expect
(
store
.
dispatch
).
toHaveBeenCalledWith
(
'
requestDeleteImage
'
,
await
waitForApolloRequestRender
();
wrapper
.
vm
.
itemToDelete
,
};
it
(
'
should call deleteItem when confirming deletion
'
,
async
()
=>
{
const
mutationResolver
=
jest
.
fn
().
mockResolvedValue
(
graphQLImageDeleteMock
);
mountComponent
({
mutationResolver
});
await
deleteImage
();
expect
(
wrapper
.
vm
.
itemToDelete
).
toEqual
(
deletedContainerRepository
);
expect
(
mutationResolver
).
toHaveBeenCalledWith
({
id
:
deletedContainerRepository
.
id
});
const
updatedImage
=
findImageList
()
.
props
(
'
images
'
)
.
find
(
i
=>
i
.
id
===
deletedContainerRepository
.
id
);
expect
(
updatedImage
.
status
).
toBe
(
deletedContainerRepository
.
status
);
});
it
(
'
should show a success alert when delete request is successful
'
,
async
()
=>
{
const
mutationResolver
=
jest
.
fn
().
mockResolvedValue
(
graphQLImageDeleteMock
);
mountComponent
({
mutationResolver
});
await
deleteImage
();
const
alert
=
findDeleteAlert
();
expect
(
alert
.
exists
()).
toBe
(
true
);
expect
(
alert
.
text
().
replace
(
/
\s\s
+/gm
,
'
'
)).
toBe
(
DELETE_IMAGE_SUCCESS_MESSAGE
.
replace
(
'
%{title}
'
,
wrapper
.
vm
.
itemToDelete
.
path
),
);
);
});
});
it
(
'
should show a success alert when delete request is successful
'
,
()
=>
{
describe
(
'
when delete request fails it shows an alert
'
,
()
=>
{
dispatchSpy
.
mockResolvedValue
();
it
(
'
user recoverable error
'
,
async
()
=>
{
findImageList
().
vm
.
$emit
(
'
delete
'
,
itemToDelete
);
const
mutationResolver
=
jest
.
fn
().
mockResolvedValue
(
graphQLImageDeleteMockError
);
expect
(
wrapper
.
vm
.
itemToDelete
).
toEqual
(
itemToDelete
);
mountComponent
({
mutationResolver
});
return
wrapper
.
vm
.
handleDeleteImage
().
then
(()
=>
{
await
deleteImage
();
const
alert
=
findDeleteAlert
();
const
alert
=
findDeleteAlert
();
expect
(
alert
.
exists
()).
toBe
(
true
);
expect
(
alert
.
exists
()).
toBe
(
true
);
expect
(
alert
.
text
().
replace
(
/
\s\s
+/gm
,
'
'
)).
toBe
(
expect
(
alert
.
text
().
replace
(
/
\s\s
+/gm
,
'
'
)).
toBe
(
DELETE_IMAGE_
SUCCESS
_MESSAGE
.
replace
(
'
%{title}
'
,
wrapper
.
vm
.
itemToDelete
.
path
),
DELETE_IMAGE_
ERROR
_MESSAGE
.
replace
(
'
%{title}
'
,
wrapper
.
vm
.
itemToDelete
.
path
),
);
);
});
});
});
it
(
'
should show an error alert when delete request fails
'
,
()
=>
{
it
(
'
network error
'
,
async
()
=>
{
dispatchSpy
.
mockRejectedValue
();
const
mutationResolver
=
jest
.
fn
().
mockRejectedValue
();
findImageList
().
vm
.
$emit
(
'
delete
'
,
itemToDelete
);
mountComponent
({
mutationResolver
});
expect
(
wrapper
.
vm
.
itemToDelete
).
toEqual
(
itemToDelete
);
return
wrapper
.
vm
.
handleDeleteImage
().
then
(()
=>
{
await
deleteImage
();
const
alert
=
findDeleteAlert
();
const
alert
=
findDeleteAlert
();
expect
(
alert
.
exists
()).
toBe
(
true
);
expect
(
alert
.
exists
()).
toBe
(
true
);
expect
(
alert
.
text
().
replace
(
/
\s\s
+/gm
,
'
'
)).
toBe
(
expect
(
alert
.
text
().
replace
(
/
\s\s
+/gm
,
'
'
)).
toBe
(
...
@@ -258,38 +336,68 @@ describe('List Page', () => {
...
@@ -258,38 +336,68 @@ describe('List Page', () => {
});
});
describe
(
'
search
'
,
()
=>
{
describe
(
'
search
'
,
()
=>
{
it
(
'
has a search box element
'
,
()
=>
{
const
doSearch
=
async
()
=>
{
await
waitForApolloRequestRender
();
findSearchBox
().
vm
.
$emit
(
'
submit
'
,
'
centos6
'
);
await
wrapper
.
vm
.
$nextTick
();
};
it
(
'
has a search box element
'
,
async
()
=>
{
mountComponent
();
mountComponent
();
await
waitForApolloRequestRender
();
const
searchBox
=
findSearchBox
();
const
searchBox
=
findSearchBox
();
expect
(
searchBox
.
exists
()).
toBe
(
true
);
expect
(
searchBox
.
exists
()).
toBe
(
true
);
expect
(
searchBox
.
attributes
(
'
placeholder
'
)).
toBe
(
SEARCH_PLACEHOLDER_TEXT
);
expect
(
searchBox
.
attributes
(
'
placeholder
'
)).
toBe
(
SEARCH_PLACEHOLDER_TEXT
);
});
});
it
(
'
performs a search
'
,
()
=>
{
it
(
'
performs a search
'
,
async
()
=>
{
mountComponent
();
const
resolver
=
jest
.
fn
().
mockResolvedValue
(
graphQLImageListMock
);
findSearchBox
().
vm
.
$emit
(
'
submit
'
,
'
foo
'
);
mountComponent
({
resolver
});
expect
(
store
.
dispatch
).
toHaveBeenCalledWith
(
'
requestImagesList
'
,
{
name
:
'
foo
'
,
await
doSearch
();
});
expect
(
resolver
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
name
:
'
centos6
'
}));
});
});
it
(
'
when search result is empty displays an empty search message
'
,
()
=>
{
it
(
'
when search result is empty displays an empty search message
'
,
async
()
=>
{
mountComponent
();
const
resolver
=
jest
.
fn
().
mockResolvedValue
(
graphQLImageListMock
);
store
.
commit
(
SET_IMAGES_LIST_SUCCESS
,
[]);
mountComponent
({
resolver
});
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findEmptySearchMessage
().
exists
()).
toBe
(
true
);
resolver
.
mockResolvedValue
(
graphQLEmptyImageListMock
);
});
await
doSearch
();
expect
(
findEmptySearchMessage
().
exists
()).
toBe
(
true
);
});
});
});
});
describe
(
'
pagination
'
,
()
=>
{
describe
(
'
pagination
'
,
()
=>
{
it
(
'
pageChange event triggers the appropriate store function
'
,
()
=>
{
it
(
'
prev-page event triggers a fetchMore request
'
,
async
()
=>
{
mountComponent
();
const
resolver
=
jest
.
fn
().
mockResolvedValue
(
graphQLImageListMock
);
findImageList
().
vm
.
$emit
(
'
pageChange
'
,
2
);
mountComponent
({
resolver
});
expect
(
store
.
dispatch
).
toHaveBeenCalledWith
(
'
requestImagesList
'
,
{
pagination
:
{
page
:
2
},
await
waitForApolloRequestRender
();
name
:
wrapper
.
vm
.
search
,
});
findImageList
().
vm
.
$emit
(
'
prev-page
'
);
expect
(
resolver
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
first
:
null
,
before
:
pageInfo
.
startCursor
}),
);
});
it
(
'
next-page event triggers a fetchMore request
'
,
async
()
=>
{
const
resolver
=
jest
.
fn
().
mockResolvedValue
(
graphQLImageListMock
);
mountComponent
({
resolver
});
await
waitForApolloRequestRender
();
findImageList
().
vm
.
$emit
(
'
next-page
'
);
expect
(
resolver
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
after
:
pageInfo
.
endCursor
}),
);
});
});
});
});
});
});
...
@@ -324,11 +432,11 @@ describe('List Page', () => {
...
@@ -324,11 +432,11 @@ describe('List Page', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
jest
.
spyOn
(
Tracking
,
'
event
'
);
jest
.
spyOn
(
Tracking
,
'
event
'
);
dispatchSpy
.
mockResolvedValue
();
});
});
it
(
'
send an event when delete button is clicked
'
,
()
=>
{
it
(
'
send an event when delete button is clicked
'
,
()
=>
{
findImageList
().
vm
.
$emit
(
'
delete
'
,
{});
findImageList
().
vm
.
$emit
(
'
delete
'
,
{});
testTrackingCall
(
'
click_button
'
);
testTrackingCall
(
'
click_button
'
);
});
});
...
...
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