Commit 3a14b9ac authored by peterhegman's avatar peterhegman

Render initially selected projects on load

On the access token form render initially selected projects. Will be
used when validation fails
parent 03a90e30
......@@ -15,7 +15,9 @@ export default {
},
data() {
return {
selectedRadio: this.$options.ALL_PROJECTS,
selectedRadio: !this.inputAttrs.value
? this.$options.ALL_PROJECTS
: this.$options.SELECTED_PROJECTS,
selectedProjects: [],
};
},
......@@ -28,6 +30,13 @@ export default {
? null
: this.selectedProjects.map((project) => project.id).join(',');
},
initialProjectIds() {
if (!this.inputAttrs.value) {
return [];
}
return this.inputAttrs.value.split(',');
},
},
methods: {
handleTokenSelectorFocus() {
......@@ -50,7 +59,11 @@ export default {
__('Selected projects')
}}</gl-form-radio>
<input :id="inputAttrs.id" type="hidden" :name="inputAttrs.name" :value="hiddenInputValue" />
<projects-token-selector v-model="selectedProjects" @focus="handleTokenSelectorFocus" />
<projects-token-selector
v-model="selectedProjects"
:initial-project-ids="initialProjectIds"
@focus="handleTokenSelectorFocus"
/>
</gl-form-group>
</div>
</template>
......@@ -8,12 +8,13 @@ import {
} from '@gitlab/ui';
import produce from 'immer';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
import getProjectsQuery from '../graphql/queries/get_projects.query.graphql';
const DEBOUNCE_DELAY = 250;
const PROJECTS_PER_PAGE = 20;
const GRAPHQL_ENTITY_TYPE = 'Project';
export default {
name: 'ProjectsTokenSelector',
......@@ -32,6 +33,10 @@ export default {
type: Array,
required: true,
},
initialProjectIds: {
type: Array,
required: true,
},
},
apollo: {
projects: {
......@@ -46,10 +51,7 @@ export default {
},
update({ projects }) {
return {
list: projects.nodes.map((project) => ({
...project,
id: getIdFromGraphQLId(project.id),
})),
list: this.formatProjectNodes(projects),
pageInfo: projects.pageInfo,
};
},
......@@ -58,6 +60,21 @@ export default {
this.isSearching = false;
},
},
initialProjects: {
query: getProjectsQuery,
variables() {
return {
ids: this.initialProjectIds.map((id) => convertToGraphQLId(GRAPHQL_ENTITY_TYPE, id)),
};
},
manual: true,
skip() {
return !this.initialProjectIds.length;
},
result({ data: { projects } }) {
this.$emit('input', this.formatProjectNodes(projects));
},
},
},
data() {
return {
......@@ -71,6 +88,12 @@ export default {
};
},
methods: {
formatProjectNodes(projects) {
return projects.nodes.map((project) => ({
...project,
id: getIdFromGraphQLId(project.id),
}));
},
handleSearch(query) {
this.isSearching = true;
this.searchQuery = query;
......
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query getProjects($search: String!, $after: String = "", $first: Int!) {
query getProjects(
$search: String = ""
$after: String = ""
$first: Int = null
$ids: [ID!] = null
) {
projects(
search: $search
after: $after
first: $first
ids: $ids
membership: true
searchNamespaces: true
sort: "UPDATED_ASC"
......
......@@ -10,6 +10,7 @@ const getInputAttrs = (el) => {
return {
id: input.id,
name: input.name,
value: input.value,
placeholder: input.placeholder,
};
};
......
......@@ -6,12 +6,13 @@ import ProjectsTokenSelector from '~/access_tokens/components/projects_token_sel
describe('ProjectsField', () => {
let wrapper;
const createComponent = () => {
const createComponent = ({ inputAttrsValue = '' } = {}) => {
wrapper = mount(ProjectsField, {
propsData: {
inputAttrs: {
id: 'projects',
name: 'projects',
value: inputAttrsValue,
},
},
});
......@@ -24,39 +25,63 @@ describe('ProjectsField', () => {
const findProjectsTokenSelector = () => wrapper.findComponent(ProjectsTokenSelector);
const findHiddenInput = () => wrapper.find('input[type="hidden"]');
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders label and sub-label', () => {
createComponent();
expect(queryByText('Projects')).not.toBe(null);
expect(queryByText('Set access permissions for this token.')).not.toBe(null);
});
it('renders "All projects" radio selected by default', () => {
const allProjectsRadio = findAllProjectsRadio();
describe('when `inputAttrs.value` is empty', () => {
beforeEach(() => {
createComponent();
});
expect(allProjectsRadio).not.toBe(null);
expect(allProjectsRadio.checked).toBe(true);
it('renders "All projects" radio as checked', () => {
expect(findAllProjectsRadio().checked).toBe(true);
});
it('renders "Selected projects" radio as unchecked', () => {
expect(findSelectedProjectsRadio().checked).toBe(false);
});
it('sets `projects-token-selector` `initialProjectIds` prop to an empty array', () => {
expect(findProjectsTokenSelector().props('initialProjectIds')).toEqual([]);
});
});
it('renders "Selected projects" radio unchecked by default', () => {
const selectedProjectsRadio = findSelectedProjectsRadio();
describe('when `inputAttrs.value` is a comma separated list of project IDs', () => {
beforeEach(() => {
createComponent({ inputAttrsValue: '1,2' });
});
it('renders "All projects" radio as unchecked', () => {
expect(findAllProjectsRadio().checked).toBe(false);
});
it('renders "Selected projects" radio as checked', () => {
expect(findSelectedProjectsRadio().checked).toBe(true);
});
expect(selectedProjectsRadio).not.toBe(null);
expect(selectedProjectsRadio.checked).toBe(false);
it('sets `projects-token-selector` `initialProjectIds` prop to an array of project IDs', () => {
expect(findProjectsTokenSelector().props('initialProjectIds')).toEqual(['1', '2']);
});
});
it('renders `projects-token-selector` component', () => {
createComponent();
expect(findProjectsTokenSelector().exists()).toBe(true);
});
it('renders hidden input with correct `name` and `id` attributes', () => {
createComponent();
expect(findHiddenInput().attributes()).toEqual(
expect.objectContaining({
id: 'projects',
......@@ -67,6 +92,8 @@ describe('ProjectsField', () => {
describe('when `projects-token-selector` is focused', () => {
beforeEach(() => {
createComponent();
findProjectsTokenSelector().vm.$emit('focus');
});
......
......@@ -44,10 +44,15 @@ describe('ProjectsTokenSelector', () => {
let wrapper;
let resolveGetProjectsQuery;
let resolveGetInitialProjectsQuery;
const getProjectsQueryRequestHandler = jest.fn(
() =>
({ ids }) =>
new Promise((resolve) => {
resolveGetProjectsQuery = resolve;
if (ids) {
resolveGetInitialProjectsQuery = resolve;
} else {
resolveGetProjectsQuery = resolve;
}
}),
);
......@@ -63,6 +68,7 @@ describe('ProjectsTokenSelector', () => {
apolloProvider,
propsData: {
selectedProjects: [],
initialProjectIds: [],
...propsData,
},
stubs: ['gl-intersection-observer'],
......@@ -156,6 +162,7 @@ describe('ProjectsTokenSelector', () => {
search: searchTerm,
after: null,
first: 20,
ids: null,
});
});
......@@ -181,6 +188,7 @@ describe('ProjectsTokenSelector', () => {
after: pageInfo.endCursor,
first: 20,
search: '',
ids: null,
});
});
......@@ -221,4 +229,41 @@ describe('ProjectsTokenSelector', () => {
expect(wrapper.emitted('focus')[0]).toEqual([event]);
});
});
describe('when `initialProjectIds` is an empty array', () => {
it('does not request initial projects', async () => {
await createComponent();
expect(getProjectsQueryRequestHandler).toHaveBeenCalledTimes(1);
expect(getProjectsQueryRequestHandler).toHaveBeenCalledWith(
expect.objectContaining({
ids: null,
}),
);
});
});
describe('when `initialProjectIds` is an array of project IDs', () => {
it('requests those projects and emits `input` event with result', async () => {
await createComponent({
propsData: {
initialProjectIds: [getIdFromGraphQLId(project1.id), getIdFromGraphQLId(project2.id)],
},
});
resolveGetInitialProjectsQuery(getProjectsQueryResponse);
await waitForPromises();
expect(getProjectsQueryRequestHandler).toHaveBeenCalledWith({
after: '',
first: null,
search: '',
ids: [project1.id, project2.id],
});
expect(wrapper.emitted('input')[0][0]).toEqual([
{ ...project1, id: getIdFromGraphQLId(project1.id) },
{ ...project2, id: getIdFromGraphQLId(project2.id) },
]);
});
});
});
......@@ -38,6 +38,7 @@ describe('access tokens', () => {
input.setAttribute('name', 'foo-bar');
input.setAttribute('id', 'foo-bar');
input.setAttribute('placeholder', 'Foo bar');
input.setAttribute('value', '1,2');
mountEl.appendChild(input);
......@@ -58,6 +59,7 @@ describe('access tokens', () => {
expect(component.props('inputAttrs')).toEqual({
name: 'foo-bar',
id: 'foo-bar',
value: '1,2',
placeholder: 'Foo bar',
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment