Commit 2fdce865 authored by Simon Knox's avatar Simon Knox

Merge branch '334807-create-feature-page' into 'master'

Create a Feature page

See merge request gitlab-org/gitlab!71294
parents b3afa361 6899531b
<script>
export default {
name: 'WorkItemRoot',
};
</script>
<template> <template>
<div></div> <div>
<router-view />
</div>
</template> </template>
export const widgetTypes = {
title: 'TITLE',
};
{"__schema":{"types":[{"kind":"INTERFACE","name":"WorkItemWidget","possibleTypes":[{"name":"TitleWidget"}]}]}}
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import createDefaultClient from '~/lib/graphql';
import workItemQuery from './work_item.query.graphql';
import introspectionQueryResultData from './fragmentTypes.json';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
export function createApolloProvider() {
Vue.use(VueApollo);
const defaultClient = createDefaultClient(
{},
{
cacheConfig: {
fragmentMatcher,
},
assumeImmutableResults: true,
},
);
defaultClient.cache.writeQuery({
query: workItemQuery,
variables: {
id: '1',
},
data: {
workItem: {
__typename: 'WorkItem',
id: '1',
type: 'FEATURE',
widgets: {
__typename: 'WorkItemWidgetConnection',
nodes: [
{
__typename: 'TitleWidget',
type: 'TITLE',
enabled: true,
// eslint-disable-next-line @gitlab/require-i18n-strings
contentText: 'Test',
},
],
},
},
},
});
return new VueApollo({
defaultClient,
});
}
enum WorkItemType {
FEATURE
}
enum WidgetType {
TITLE
}
interface WorkItemWidget {
type: WidgetType!
}
# Replicating Relay connection type for client schema
type WorkItemWidgetEdge {
cursor: String!
node: WorkItemWidget
}
type WorkItemWidgetConnection {
edges: [WorkItemWidgetEdge]
nodes: [WorkItemWidget]
pageInfo: PageInfo!
}
type TitleWidget implements WorkItemWidget {
type: WidgetType!
contentText: String!
}
type WorkItem {
id: ID!
type: WorkItemType!
widgets: [WorkItemWidgetConnection]
}
extend type Query {
workItem(id: ID!): WorkItem!
}
fragment WidgetBase on WorkItemWidget {
type
}
#import './widget.fragment.graphql'
query WorkItem($id: ID!) {
workItem(id: $id) @client {
id
type
widgets {
nodes {
...WidgetBase
... on TitleWidget {
contentText
}
}
}
}
}
import Vue from 'vue'; import Vue from 'vue';
import App from './components/app.vue'; import App from './components/app.vue';
import { createRouter } from './router';
import { createApolloProvider } from './graphql/provider';
export const initWorkItemsRoot = () => { export const initWorkItemsRoot = () => {
const el = document.querySelector('#js-work-items'); const el = document.querySelector('#js-work-items');
return new Vue({ return new Vue({
el, el,
router: createRouter(el.dataset.fullPath),
apolloProvider: createApolloProvider(),
render(createElement) { render(createElement) {
return createElement(App); return createElement(App);
}, },
......
<script>
import workItemQuery from '../graphql/work_item.query.graphql';
import { widgetTypes } from '../constants';
export default {
props: {
id: {
type: String,
required: true,
},
},
data() {
return {
workItem: null,
};
},
apollo: {
workItem: {
query: workItemQuery,
variables() {
return {
id: this.id,
};
},
},
},
computed: {
titleWidgetData() {
return this.workItem?.widgets?.nodes?.find((widget) => widget.type === widgetTypes.title);
},
},
};
</script>
<template>
<section>
<!-- Title widget placeholder -->
<div>
<h2 v-if="titleWidgetData" class="title" data-testid="title">
{{ titleWidgetData.contentText }}
</h2>
</div>
</section>
</template>
import Vue from 'vue';
import VueRouter from 'vue-router';
import { joinPaths } from '~/lib/utils/url_utility';
import { routes } from './routes';
Vue.use(VueRouter);
export function createRouter(fullPath) {
return new VueRouter({
routes,
mode: 'history',
base: joinPaths(fullPath, '-', 'work_items'),
});
}
export const routes = [
{
path: '/:id',
name: 'work_item',
component: () => import('../pages/work_item_root.vue'),
props: true,
},
];
- page_title s_('WorkItem|Work Items') - page_title s_('WorkItem|Work Items')
#js-work-items #js-work-items{ data: { full_path: @project.full_path } }
...@@ -358,7 +358,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -358,7 +358,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get 'details', on: :member get 'details', on: :member
end end
resources :work_items, only: [:index] get 'work_items/*work_items_path' => 'work_items#index', as: :work_items
resource :tracing, only: [:show] resource :tracing, only: [:show]
......
import { shallowMount } from '@vue/test-utils';
import App from '~/work_items/components/app.vue';
describe('Work Items Application', () => {
let wrapper;
const createComponent = () => {
wrapper = shallowMount(App, {
stubs: {
'router-view': true,
},
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders a component', () => {
createComponent();
expect(wrapper.exists()).toBe(true);
});
});
export const workItemQueryResponse = {
workItem: {
__typename: 'WorkItem',
id: '1',
type: 'FEATURE',
widgets: {
__typename: 'WorkItemWidgetConnection',
nodes: [
{
__typename: 'TitleWidget',
type: 'TITLE',
contentText: 'Test',
},
],
},
},
};
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
import WorkItemsRoot from '~/work_items/pages/work_item_root.vue';
import { workItemQueryResponse } from '../mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
const WORK_ITEM_ID = '1';
describe('Work items root component', () => {
let wrapper;
let fakeApollo;
const findTitle = () => wrapper.find('[data-testid="title"]');
const createComponent = ({ queryResponse = workItemQueryResponse } = {}) => {
fakeApollo = createMockApollo();
fakeApollo.clients.defaultClient.cache.writeQuery({
query: workItemQuery,
variables: {
id: WORK_ITEM_ID,
},
data: queryResponse,
});
wrapper = shallowMount(WorkItemsRoot, {
propsData: {
id: WORK_ITEM_ID,
},
localVue,
apolloProvider: fakeApollo,
});
};
afterEach(() => {
wrapper.destroy();
fakeApollo = null;
});
it('renders the title if title is in the widgets list', () => {
createComponent();
expect(findTitle().exists()).toBe(true);
expect(findTitle().text()).toBe('Test');
});
it('does not render the title if title is not in the widgets list', () => {
const queryResponse = {
workItem: {
...workItemQueryResponse.workItem,
widgets: {
__typename: 'WorkItemWidgetConnection',
nodes: [
{
__typename: 'SomeOtherWidget',
type: 'OTHER',
contentText: 'Test',
},
],
},
},
};
createComponent({ queryResponse });
expect(findTitle().exists()).toBe(false);
});
});
import { mount } from '@vue/test-utils';
import App from '~/work_items/components/app.vue';
import WorkItemsRoot from '~/work_items/pages/work_item_root.vue';
import { createRouter } from '~/work_items/router';
describe('Work items router', () => {
let wrapper;
const createComponent = async (routeArg) => {
const router = createRouter('/work_item');
if (routeArg !== undefined) {
await router.push(routeArg);
}
wrapper = mount(App, {
router,
});
};
afterEach(() => {
wrapper.destroy();
window.location.hash = '';
});
it('renders work item on `/1` route', async () => {
await createComponent('/1');
expect(wrapper.find(WorkItemsRoot).exists()).toBe(true);
});
});
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