summaryrefslogtreecommitdiff
path: root/spec/frontend/pages
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-10 09:08:10 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-10 09:08:10 +0000
commit82fa8a3d1e8466ef36b58604d20fcc145ea12118 (patch)
treec5c0286537405c2fa7719ecce3ed0d73d947c555 /spec/frontend/pages
parent232655bf32cd474d54de357b65ef43d77271117c (diff)
downloadgitlab-ce-82fa8a3d1e8466ef36b58604d20fcc145ea12118.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/pages')
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/project_feature_settings_spec.js124
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/project_setting_row_spec.js63
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js434
3 files changed, 621 insertions, 0 deletions
diff --git a/spec/frontend/pages/projects/shared/permissions/components/project_feature_settings_spec.js b/spec/frontend/pages/projects/shared/permissions/components/project_feature_settings_spec.js
new file mode 100644
index 00000000000..8ab5426a005
--- /dev/null
+++ b/spec/frontend/pages/projects/shared/permissions/components/project_feature_settings_spec.js
@@ -0,0 +1,124 @@
+import { mount, shallowMount } from '@vue/test-utils';
+
+import projectFeatureSetting from '~/pages/projects/shared/permissions/components/project_feature_setting.vue';
+import projectFeatureToggle from '~/vue_shared/components/toggle_button.vue';
+
+describe('Project Feature Settings', () => {
+ const defaultProps = {
+ name: 'Test',
+ options: [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]],
+ value: 1,
+ disabledInput: false,
+ };
+ let wrapper;
+
+ const mountComponent = customProps => {
+ const propsData = { ...defaultProps, ...customProps };
+ return shallowMount(projectFeatureSetting, { propsData });
+ };
+
+ beforeEach(() => {
+ wrapper = mountComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('Hidden name input', () => {
+ it('should set the hidden name input if the name exists', () => {
+ expect(wrapper.find({ name: 'Test' }).props().value).toBe(1);
+ });
+
+ it('should not set the hidden name input if the name does not exist', () => {
+ wrapper.setProps({ name: null });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ name: 'Test' }).exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('Feature toggle', () => {
+ it('should enable the feature toggle if the value is not 0', () => {
+ expect(wrapper.find(projectFeatureToggle).props().value).toBe(true);
+ });
+
+ it('should enable the feature toggle if the value is less than 0', () => {
+ wrapper.setProps({ value: -1 });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find(projectFeatureToggle).props().value).toBe(true);
+ });
+ });
+
+ it('should disable the feature toggle if the value is 0', () => {
+ wrapper.setProps({ value: 0 });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find(projectFeatureToggle).props().value).toBe(false);
+ });
+ });
+
+ it('should disable the feature toggle if disabledInput is set', () => {
+ wrapper.setProps({ disabledInput: true });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find(projectFeatureToggle).props().disabledInput).toBe(true);
+ });
+ });
+
+ it('should emit a change event when the feature toggle changes', () => {
+ // Needs to be fully mounted to be able to trigger the click event on the internal button
+ wrapper = mount(projectFeatureSetting, { propsData: defaultProps });
+
+ expect(wrapper.emitted().change).toBeUndefined();
+ wrapper
+ .find(projectFeatureToggle)
+ .find('button')
+ .trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted().change.length).toBe(1);
+ expect(wrapper.emitted().change[0]).toEqual([0]);
+ });
+ });
+ });
+
+ describe('Project repo select', () => {
+ it.each`
+ disabledInput | value | options | isDisabled
+ ${true} | ${0} | ${[[1, 1]]} | ${true}
+ ${true} | ${1} | ${[[1, 1], [2, 2], [3, 3]]} | ${true}
+ ${false} | ${0} | ${[[1, 1], [2, 2], [3, 3]]} | ${true}
+ ${false} | ${1} | ${[[1, 1]]} | ${true}
+ ${false} | ${1} | ${[[1, 1], [2, 2], [3, 3]]} | ${false}
+ `(
+ 'should set disabled to $isDisabled when disabledInput is $disabledInput, the value is $value and options are $options',
+ ({ disabledInput, value, options, isDisabled }) => {
+ wrapper.setProps({ disabledInput, value, options });
+
+ return wrapper.vm.$nextTick(() => {
+ if (isDisabled) {
+ expect(wrapper.find('select').attributes().disabled).toEqual('disabled');
+ } else {
+ expect(wrapper.find('select').attributes().disabled).toBeUndefined();
+ }
+ });
+ },
+ );
+
+ it('should emit the change when a new option is selected', () => {
+ expect(wrapper.emitted().change).toBeUndefined();
+ wrapper
+ .findAll('option')
+ .at(1)
+ .trigger('change');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted().change.length).toBe(1);
+ expect(wrapper.emitted().change[0]).toEqual([2]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pages/projects/shared/permissions/components/project_setting_row_spec.js b/spec/frontend/pages/projects/shared/permissions/components/project_setting_row_spec.js
new file mode 100644
index 00000000000..7cbcbdcdd1f
--- /dev/null
+++ b/spec/frontend/pages/projects/shared/permissions/components/project_setting_row_spec.js
@@ -0,0 +1,63 @@
+import { shallowMount } from '@vue/test-utils';
+
+import projectSettingRow from '~/pages/projects/shared/permissions/components/project_setting_row.vue';
+
+describe('Project Setting Row', () => {
+ let wrapper;
+
+ const mountComponent = (customProps = {}) => {
+ const propsData = { ...customProps };
+ return shallowMount(projectSettingRow, { propsData });
+ };
+
+ beforeEach(() => {
+ wrapper = mountComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should show the label if it is set', () => {
+ wrapper.setProps({ label: 'Test label' });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find('label').text()).toEqual('Test label');
+ });
+ });
+
+ it('should hide the label if it is not set', () => {
+ expect(wrapper.find('label').exists()).toBe(false);
+ });
+
+ it('should show the help icon with the correct help path if it is set', () => {
+ wrapper.setProps({ label: 'Test label', helpPath: '/123' });
+
+ return wrapper.vm.$nextTick(() => {
+ const link = wrapper.find('a');
+
+ expect(link.exists()).toBe(true);
+ expect(link.attributes().href).toEqual('/123');
+ });
+ });
+
+ it('should hide the help icon if no help path is set', () => {
+ wrapper.setProps({ label: 'Test label' });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find('a').exists()).toBe(false);
+ });
+ });
+
+ it('should show the help text if it is set', () => {
+ wrapper.setProps({ helpText: 'Test text' });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find('span').text()).toEqual('Test text');
+ });
+ });
+
+ it('should hide the help text if it is set', () => {
+ expect(wrapper.find('span').exists()).toBe(false);
+ });
+});
diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
new file mode 100644
index 00000000000..c304dfd2048
--- /dev/null
+++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
@@ -0,0 +1,434 @@
+import { shallowMount } from '@vue/test-utils';
+
+import settingsPanel from '~/pages/projects/shared/permissions/components/settings_panel.vue';
+import {
+ featureAccessLevel,
+ visibilityLevelDescriptions,
+ visibilityOptions,
+} from '~/pages/projects/shared/permissions/constants';
+
+const defaultProps = {
+ currentSettings: {
+ visibilityLevel: 10,
+ requestAccessEnabled: true,
+ issuesAccessLevel: 20,
+ repositoryAccessLevel: 20,
+ forkingAccessLevel: 20,
+ mergeRequestsAccessLevel: 20,
+ buildsAccessLevel: 20,
+ wikiAccessLevel: 20,
+ snippetsAccessLevel: 20,
+ pagesAccessLevel: 10,
+ containerRegistryEnabled: true,
+ lfsEnabled: true,
+ emailsDisabled: false,
+ packagesEnabled: true,
+ },
+ canDisableEmails: true,
+ canChangeVisibilityLevel: true,
+ allowedVisibilityOptions: [0, 10, 20],
+ visibilityHelpPath: '/help/public_access/public_access',
+ registryAvailable: false,
+ registryHelpPath: '/help/user/packages/container_registry/index',
+ lfsAvailable: true,
+ lfsHelpPath: '/help/workflow/lfs/manage_large_binaries_with_git_lfs',
+ pagesAvailable: true,
+ pagesAccessControlEnabled: false,
+ pagesAccessControlForced: false,
+ pagesHelpPath: '/help/user/project/pages/introduction#gitlab-pages-access-control-core',
+ packagesAvailable: false,
+ packagesHelpPath: '/help/user/packages/index',
+};
+
+describe('Settings Panel', () => {
+ let wrapper;
+
+ const mountComponent = customProps => {
+ const propsData = { ...defaultProps, ...customProps };
+ return shallowMount(settingsPanel, { propsData });
+ };
+
+ const overrideCurrentSettings = (currentSettingsProps, extraProps = {}) => {
+ return mountComponent({
+ ...extraProps,
+ currentSettings: {
+ ...defaultProps.currentSettings,
+ ...currentSettingsProps,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ wrapper = mountComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('Project Visibility', () => {
+ it('should set the project visibility help path', () => {
+ expect(wrapper.find({ ref: 'project-visibility-settings' }).props().helpPath).toBe(
+ defaultProps.visibilityHelpPath,
+ );
+ });
+
+ it('should not disable the visibility level dropdown', () => {
+ wrapper.setProps({ canChangeVisibilityLevel: true });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(
+ wrapper.find('[name="project[visibility_level]"]').attributes().disabled,
+ ).toBeUndefined();
+ });
+ });
+
+ it('should disable the visibility level dropdown', () => {
+ wrapper.setProps({ canChangeVisibilityLevel: false });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find('[name="project[visibility_level]"]').attributes().disabled).toBe(
+ 'disabled',
+ );
+ });
+ });
+
+ it.each`
+ option | allowedOptions | disabled
+ ${visibilityOptions.PRIVATE} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false}
+ ${visibilityOptions.PRIVATE} | ${[visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${true}
+ ${visibilityOptions.INTERNAL} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false}
+ ${visibilityOptions.INTERNAL} | ${[visibilityOptions.PRIVATE, visibilityOptions.PUBLIC]} | ${true}
+ ${visibilityOptions.PUBLIC} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false}
+ ${visibilityOptions.PUBLIC} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL]} | ${true}
+ `(
+ 'sets disabled to $disabled for the visibility option $option when given $allowedOptions',
+ ({ option, allowedOptions, disabled }) => {
+ wrapper.setProps({ allowedVisibilityOptions: allowedOptions });
+
+ return wrapper.vm.$nextTick(() => {
+ const attributeValue = wrapper
+ .find(`[name="project[visibility_level]"] option[value="${option}"]`)
+ .attributes().disabled;
+
+ if (disabled) {
+ expect(attributeValue).toBe('disabled');
+ } else {
+ expect(attributeValue).toBeUndefined();
+ }
+ });
+ },
+ );
+
+ it('should set the visibility level description based upon the selected visibility level', () => {
+ wrapper.find('[name="project[visibility_level]"]').setValue(visibilityOptions.INTERNAL);
+
+ expect(wrapper.find({ ref: 'project-visibility-settings' }).text()).toContain(
+ visibilityLevelDescriptions[visibilityOptions.INTERNAL],
+ );
+ });
+
+ it('should show the request access checkbox if the visibility level is not private', () => {
+ wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.INTERNAL });
+
+ expect(wrapper.find('[name="project[request_access_enabled]"]').exists()).toBe(true);
+ });
+
+ it('should not show the request access checkbox if the visibility level is private', () => {
+ wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PRIVATE });
+
+ expect(wrapper.find('[name="project[request_access_enabled]"]').exists()).toBe(false);
+ });
+ });
+
+ describe('Repository', () => {
+ it('should set the repository help text when the visibility level is set to private', () => {
+ wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PRIVATE });
+
+ expect(wrapper.find({ ref: 'repository-settings' }).props().helpText).toEqual(
+ 'View and edit files in this project',
+ );
+ });
+
+ it('should set the repository help text with a read access warning when the visibility level is set to non-private', () => {
+ wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PUBLIC });
+
+ expect(wrapper.find({ ref: 'repository-settings' }).props().helpText).toEqual(
+ 'View and edit files in this project. Non-project members will only have read access',
+ );
+ });
+ });
+
+ describe('Merge requests', () => {
+ it('should enable the merge requests access level input when the repository is enabled', () => {
+ wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.EVERYONE });
+
+ expect(
+ wrapper
+ .find('[name="project[project_feature_attributes][merge_requests_access_level]"]')
+ .props().disabledInput,
+ ).toEqual(false);
+ });
+
+ it('should disable the merge requests access level input when the repository is disabled', () => {
+ wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.NOT_ENABLED });
+
+ expect(
+ wrapper
+ .find('[name="project[project_feature_attributes][merge_requests_access_level]"]')
+ .props().disabledInput,
+ ).toEqual(true);
+ });
+ });
+
+ describe('Forks', () => {
+ it('should enable the forking access level input when the repository is enabled', () => {
+ wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.EVERYONE });
+
+ expect(
+ wrapper.find('[name="project[project_feature_attributes][forking_access_level]"]').props()
+ .disabledInput,
+ ).toEqual(false);
+ });
+
+ it('should disable the forking access level input when the repository is disabled', () => {
+ wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.NOT_ENABLED });
+
+ expect(
+ wrapper.find('[name="project[project_feature_attributes][forking_access_level]"]').props()
+ .disabledInput,
+ ).toEqual(true);
+ });
+ });
+
+ describe('Pipelines', () => {
+ it('should enable the builds access level input when the repository is enabled', () => {
+ wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.EVERYONE });
+
+ expect(
+ wrapper.find('[name="project[project_feature_attributes][builds_access_level]"]').props()
+ .disabledInput,
+ ).toEqual(false);
+ });
+
+ it('should disable the builds access level input when the repository is disabled', () => {
+ wrapper = overrideCurrentSettings({ repositoryAccessLevel: featureAccessLevel.NOT_ENABLED });
+
+ expect(
+ wrapper.find('[name="project[project_feature_attributes][builds_access_level]"]').props()
+ .disabledInput,
+ ).toEqual(true);
+ });
+ });
+
+ describe('Container registry', () => {
+ it('should show the container registry settings if the registry is available', () => {
+ wrapper.setProps({ registryAvailable: true });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ ref: 'container-registry-settings' }).exists()).toBe(true);
+ });
+ });
+
+ it('should hide the container registry settings if the registry is not available', () => {
+ wrapper.setProps({ registryAvailable: false });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ ref: 'container-registry-settings' }).exists()).toBe(false);
+ });
+ });
+
+ it('should set the container registry settings help path', () => {
+ wrapper.setProps({ registryAvailable: true });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ ref: 'container-registry-settings' }).props().helpPath).toBe(
+ defaultProps.registryHelpPath,
+ );
+ });
+ });
+
+ it('should show the container registry public note if the visibility level is public and the registry is available', () => {
+ wrapper = overrideCurrentSettings(
+ { visibilityLevel: visibilityOptions.PUBLIC },
+ { registryAvailable: true },
+ );
+
+ expect(wrapper.find({ ref: 'container-registry-settings' }).text()).toContain(
+ 'Note: the container registry is always visible when a project is public',
+ );
+ });
+
+ it('should hide the container registry public note if the visibility level is private and the registry is available', () => {
+ wrapper = overrideCurrentSettings(
+ { visibilityLevel: visibilityOptions.PRIVATE },
+ { registryAvailable: true },
+ );
+
+ expect(wrapper.find({ ref: 'container-registry-settings' }).text()).not.toContain(
+ 'Note: the container registry is always visible when a project is public',
+ );
+ });
+
+ it('should enable the container registry input when the repository is enabled', () => {
+ wrapper = overrideCurrentSettings(
+ { repositoryAccessLevel: featureAccessLevel.EVERYONE },
+ { registryAvailable: true },
+ );
+
+ expect(
+ wrapper.find('[name="project[container_registry_enabled]"]').props().disabledInput,
+ ).toEqual(false);
+ });
+
+ it('should disable the container registry input when the repository is disabled', () => {
+ wrapper = overrideCurrentSettings(
+ { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
+ { registryAvailable: true },
+ );
+
+ expect(
+ wrapper.find('[name="project[container_registry_enabled]"]').props().disabledInput,
+ ).toEqual(true);
+ });
+ });
+
+ describe('Git Large File Storage', () => {
+ it('should show the LFS settings if LFS is available', () => {
+ wrapper.setProps({ lfsAvailable: true });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ ref: 'git-lfs-settings' }).exists()).toEqual(true);
+ });
+ });
+
+ it('should hide the LFS settings if LFS is not available', () => {
+ wrapper.setProps({ lfsAvailable: false });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ ref: 'git-lfs-settings' }).exists()).toEqual(false);
+ });
+ });
+
+ it('should set the LFS settings help path', () => {
+ expect(wrapper.find({ ref: 'git-lfs-settings' }).props().helpPath).toBe(
+ defaultProps.lfsHelpPath,
+ );
+ });
+
+ it('should enable the LFS input when the repository is enabled', () => {
+ wrapper = overrideCurrentSettings(
+ { repositoryAccessLevel: featureAccessLevel.EVERYONE },
+ { lfsAvailable: true },
+ );
+
+ expect(wrapper.find('[name="project[lfs_enabled]"]').props().disabledInput).toEqual(false);
+ });
+
+ it('should disable the LFS input when the repository is disabled', () => {
+ wrapper = overrideCurrentSettings(
+ { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
+ { lfsAvailable: true },
+ );
+
+ expect(wrapper.find('[name="project[lfs_enabled]"]').props().disabledInput).toEqual(true);
+ });
+ });
+
+ describe('Packages', () => {
+ it('should show the packages settings if packages are available', () => {
+ wrapper.setProps({ packagesAvailable: true });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ ref: 'package-settings' }).exists()).toEqual(true);
+ });
+ });
+
+ it('should hide the packages settings if packages are not available', () => {
+ wrapper.setProps({ packagesAvailable: false });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ ref: 'package-settings' }).exists()).toEqual(false);
+ });
+ });
+
+ it('should set the package settings help path', () => {
+ wrapper.setProps({ packagesAvailable: true });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ ref: 'package-settings' }).props().helpPath).toBe(
+ defaultProps.packagesHelpPath,
+ );
+ });
+ });
+
+ it('should enable the packages input when the repository is enabled', () => {
+ wrapper = overrideCurrentSettings(
+ { repositoryAccessLevel: featureAccessLevel.EVERYONE },
+ { packagesAvailable: true },
+ );
+
+ expect(wrapper.find('[name="project[packages_enabled]"]').props().disabledInput).toEqual(
+ false,
+ );
+ });
+
+ it('should disable the packages input when the repository is disabled', () => {
+ wrapper = overrideCurrentSettings(
+ { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED },
+ { packagesAvailable: true },
+ );
+
+ expect(wrapper.find('[name="project[packages_enabled]"]').props().disabledInput).toEqual(
+ true,
+ );
+ });
+ });
+
+ describe('Pages', () => {
+ it.each`
+ pagesAvailable | pagesAccessControlEnabled | visibility
+ ${true} | ${true} | ${'show'}
+ ${true} | ${false} | ${'hide'}
+ ${false} | ${true} | ${'hide'}
+ ${false} | ${false} | ${'hide'}
+ `(
+ 'should $visibility the page settings if pagesAvailable is $pagesAvailable and pagesAccessControlEnabled is $pagesAccessControlEnabled',
+ ({ pagesAvailable, pagesAccessControlEnabled, visibility }) => {
+ wrapper.setProps({ pagesAvailable, pagesAccessControlEnabled });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ ref: 'pages-settings' }).exists()).toBe(visibility === 'show');
+ });
+ },
+ );
+
+ it('should set the pages settings help path', () => {
+ wrapper.setProps({ pagesAvailable: true, pagesAccessControlEnabled: true });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ ref: 'pages-settings' }).props().helpPath).toBe(
+ defaultProps.pagesHelpPath,
+ );
+ });
+ });
+ });
+
+ describe('Email notifications', () => {
+ it('should show the disable email notifications input if emails an be disabled', () => {
+ wrapper.setProps({ canDisableEmails: true });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ ref: 'email-settings' }).exists()).toBe(true);
+ });
+ });
+
+ it('should hide the disable email notifications input if emails cannot be disabled', () => {
+ wrapper.setProps({ canDisableEmails: false });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find({ ref: 'email-settings' }).exists()).toBe(false);
+ });
+ });
+ });
+});