Commit 6899531b authored by Natalia Tepluhina's avatar Natalia Tepluhina

Added feature page

Added mock changes for routing

Route to singular feature by default

Revert "Route to singular feature by default"

This reverts commit a186aafedf63b804dcddaa55a5e55853b3a822fe.
Added routes for the feature page

Fixed Ruby routing

Added work item wrapper component

Added basic types for the feature

Added query for work items to schema

Moved a router

Changed routing for work item

Added typedef for work item type

Changed schema to array of widgets

Added type for title

Added apollo client to work item

Added fetching work item from cache

Added computed for title widget

Added fragment and removed enabled

Changed content to contentText

Updated the initial query

Added dynamic base path

Fixed widget type to be a connection

Disabled linted for mock response

Fixed extending type query

Added basic spec for app

Created basic router spec

Created basic test for work item root

Added test with apollo client

Added test for work item false query

Apply 1 suggestion(s) to 1 file(s)
Removed resource for work items

Moved use router outside the factory

Renamed query file

Fixed imports

Fixed import in test

Apply 1 suggestion(s) to 1 file(s)
parent a9dbb0ab
<script>
export default {
name: 'WorkItemRoot',
};
</script>
<template>
<div></div>
<div>
<router-view />
</div>
</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 App from './components/app.vue';
import { createRouter } from './router';
import { createApolloProvider } from './graphql/provider';
export const initWorkItemsRoot = () => {
const el = document.querySelector('#js-work-items');
return new Vue({
el,
router: createRouter(el.dataset.fullPath),
apolloProvider: createApolloProvider(),
render(createElement) {
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')
#js-work-items
#js-work-items{ data: { full_path: @project.full_path } }
......@@ -358,7 +358,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get 'details', on: :member
end
resources :work_items, only: [:index]
get 'work_items/*work_items_path' => 'work_items#index', as: :work_items
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