Repository: learningequality/kolibri

Issue:
add facility setting to disable 'guest' browsing

### Observed behavior

Some organizations prefer that their users always be logged in

### Expected behavior

A facility setting can be added, e.g. "Disable guest access".

When this setting is active:

* "Access as guest" is hidden on the log-in page
* When a user visits a page without an active session, they are redirected to the log-in page

### User-facing consequences

Sometimes students are accessing as a guest because it's easier than dealing with a log-in

### Context

k0.10.0


----

cc @jtamiace @rtibbles this seem reasonable?




Discussion:
Seems reasonable to me. The setup wizard would be a touchpoint for this change, so will think about how this new setting should fit in
It might make sense for this to be the third step in the setup wizard

<img width="1163" alt="screen shot 2018-07-16 at 2 27 39 pm" src="https://user-images.githubusercontent.com/6668144/42784564-7737c794-8904-11e8-93ff-7c8e0d8ef37f.png">

Here's the rest of the onboarding prototype for review
https://www.figma.com/file/CiaGXfI6OMOjBF9e8hr1c1Ap/Onboarding-0.11-updates


Resolving patch:
diff --git a/kolibri/core/device/serializers.py b/kolibri/core/device/serializers.py
--- a/kolibri/core/device/serializers.py
+++ b/kolibri/core/device/serializers.py
@@ -37,9 +37,10 @@ class DeviceProvisionSerializer(serializers.Serializer):
     preset = serializers.ChoiceField(choices=choices)
     superuser = NoFacilityFacilityUserSerializer()
     language_id = serializers.CharField(max_length=15)
+    settings = serializers.JSONField()
 
     class Meta:
-        fields = ('facility', 'dataset', 'superuser', 'language_id')
+        fields = ('facility', 'dataset', 'superuser', 'language_id', 'settings',)
 
     def validate_language_id(self, language_id):
         """
@@ -64,6 +65,10 @@ def create(self, validated_data):
             dataset_data = mappings[preset]
             for key, value in dataset_data.items():
                 setattr(facility.dataset, key, value)
+            # overwrite the settings in dataset_data with validated_data.settings
+            custom_settings = validated_data.pop('settings')
+            for key, value in custom_settings.items():
+                setattr(facility.dataset, key, value)
             facility.dataset.save()
             superuser_data = validated_data.pop('superuser')
             superuser_data['facility'] = facility
@@ -82,5 +87,6 @@ def create(self, validated_data):
                 "facility": facility,
                 "preset": preset,
                 "superuser": superuser,
-                "language_id": language_id
+                "language_id": language_id,
+                "settings": custom_settings,
             }


Test patch:
diff --git a/kolibri/core/device/test/test_api.py b/kolibri/core/device/test/test_api.py
--- a/kolibri/core/device/test/test_api.py
+++ b/kolibri/core/device/test/test_api.py
@@ -40,6 +40,7 @@ class DeviceProvisionTestCase(APITestCase):
         "learner_can_delete_account": True,
         "learner_can_login_with_no_password": False,
     }
+    settings = {}
 
     language_id = "en"
 
@@ -49,6 +50,7 @@ def test_cannot_post_if_provisioned(self):
             "superuser": self.superuser_data,
             "facility": self.facility_data,
             "preset": self.preset_data,
+            "settings": self.settings,
             "language_id": self.language_id,
         }
         response = self.client.post(reverse('deviceprovision'), data, format="json")
@@ -59,6 +61,7 @@ def test_superuser_created(self):
             "superuser": self.superuser_data,
             "facility": self.facility_data,
             "preset": self.preset_data,
+            "settings": self.settings,
             "language_id": self.language_id,
         }
         self.client.post(reverse('deviceprovision'), data, format="json")
@@ -68,6 +71,7 @@ def test_superuser_password_set_correctly(self):
         data = {
             "superuser": self.superuser_data,
             "facility": self.facility_data,
+            "settings": self.settings,
             "preset": self.preset_data,
             "language_id": self.language_id,
         }
@@ -78,6 +82,7 @@ def test_superuser_device_permissions_created(self):
         data = {
             "superuser": self.superuser_data,
             "facility": self.facility_data,
+            "settings": self.settings,
             "preset": self.preset_data,
             "language_id": self.language_id,
         }
@@ -88,6 +93,7 @@ def test_facility_created(self):
         data = {
             "superuser": self.superuser_data,
             "facility": self.facility_data,
+            "settings": self.settings,
             "preset": self.preset_data,
             "language_id": self.language_id,
         }
@@ -98,6 +104,7 @@ def test_admin_role_created(self):
         data = {
             "superuser": self.superuser_data,
             "facility": self.facility_data,
+            "settings": self.settings,
             "preset": self.preset_data,
             "language_id": self.language_id,
         }
@@ -108,6 +115,7 @@ def test_facility_role_created(self):
         data = {
             "superuser": self.superuser_data,
             "facility": self.facility_data,
+            "settings": self.settings,
             "preset": self.preset_data,
             "language_id": self.language_id,
         }
@@ -118,6 +126,7 @@ def test_dataset_set_created(self):
         data = {
             "superuser": self.superuser_data,
             "facility": self.facility_data,
+            "settings": self.settings,
             "preset": self.preset_data,
             "language_id": self.language_id,
         }
@@ -133,6 +142,7 @@ def test_device_settings_created(self):
         data = {
             "superuser": self.superuser_data,
             "facility": self.facility_data,
+            "settings": self.settings,
             "preset": self.preset_data,
             "language_id": self.language_id,
         }
@@ -144,6 +154,7 @@ def test_device_settings_default_facility_set(self):
         data = {
             "superuser": self.superuser_data,
             "facility": self.facility_data,
+            "settings": self.settings,
             "preset": self.preset_data,
             "language_id": self.language_id,
         }
diff --git a/kolibri/plugins/setup_wizard/assets/test/makeStore.js b/kolibri/plugins/setup_wizard/assets/test/makeStore.js
new file mode 100644
--- /dev/null
+++ b/kolibri/plugins/setup_wizard/assets/test/makeStore.js
@@ -0,0 +1,6 @@
+import { coreStoreFactory } from 'kolibri.coreVue.vuex.store';
+import pluginModule from '../src/modules/pluginModule';
+
+export default function makeStore() {
+  return coreStoreFactory(pluginModule);
+}
diff --git a/kolibri/plugins/setup_wizard/assets/test/views/CreateLearnerAccountForm.spec.js b/kolibri/plugins/setup_wizard/assets/test/views/CreateLearnerAccountForm.spec.js
new file mode 100644
--- /dev/null
+++ b/kolibri/plugins/setup_wizard/assets/test/views/CreateLearnerAccountForm.spec.js
@@ -0,0 +1,50 @@
+import { mount } from '@vue/test-utils';
+import makeStore from '../makeStore';
+import CreateLearnerAccountForm from '../../src/views/onboarding-forms/CreateLearnerAccountForm';
+
+function makeWrapper(options) {
+  const store = makeStore();
+  if (options.preset) {
+    store.commit('SET_FACILITY_PRESET', options.preset);
+  }
+  if (options.previousChoice !== undefined) {
+    store.commit('SET_LEARNER_CAN_SIGN_UP', options.previousChoice);
+  }
+  const wrapper = mount(CreateLearnerAccountForm, {
+    store,
+  });
+  jest.spyOn(wrapper.vm, '$emit');
+
+  return { wrapper, store };
+}
+
+describe('CreateLearnerAccountForm', () => {
+  it('has the correct default with "nonformal" preset', () => {
+    const { wrapper } = makeWrapper({ preset: 'nonformal' });
+    expect(wrapper.vm.settingIsEnabled).toEqual(true);
+  });
+
+  it('has the correct default with "formal" preset', () => {
+    const { wrapper } = makeWrapper({ preset: 'formal' });
+    expect(wrapper.vm.settingIsEnabled).toEqual(false);
+  });
+
+  it('has the correct default with "personal" preset', () => {
+    const { wrapper } = makeWrapper({ preset: 'personal' });
+    expect(wrapper.vm.settingIsEnabled).toEqual(true);
+  });
+
+  it('if user has set it in a previous step, it is kept', () => {
+    const { wrapper } = makeWrapper({ preset: 'nonformal', previousChoice: false });
+    expect(wrapper.vm.settingIsEnabled).toEqual(false);
+  });
+
+  it('after clicking submit, the setting in vuex is updated', () => {
+    const { wrapper, store } = makeWrapper({ preset: 'formal' });
+    wrapper.find({ name: 'YesNoForm' }).vm.emitSetting();
+    expect(store.state.onboardingData.settings.learner_can_sign_up).toEqual(false);
+    expect(store.state.onboardingData.settings.learner_can_edit_name).toEqual(false);
+    expect(store.state.onboardingData.settings.learner_can_edit_username).toEqual(false);
+    expect(wrapper.vm.$emit).toHaveBeenCalledTimes(1);
+  });
+});
diff --git a/kolibri/plugins/setup_wizard/assets/test/views/FacilityPermissionsForm.spec.js b/kolibri/plugins/setup_wizard/assets/test/views/FacilityPermissionsForm.spec.js
new file mode 100644
--- /dev/null
+++ b/kolibri/plugins/setup_wizard/assets/test/views/FacilityPermissionsForm.spec.js
@@ -0,0 +1,109 @@
+import { mount } from '@vue/test-utils';
+import makeStore from '../makeStore';
+import FacilityPermissionsForm from '../../src/views/onboarding-forms/FacilityPermissionsForm';
+
+function makeWrapper() {
+  const store = makeStore();
+  const wrapper = mount(FacilityPermissionsForm, {
+    store,
+  });
+  // prettier-ignore
+  const els = {
+    nonFormalRadioButton: () => wrapper.findAll({ name: 'KRadioButton' }).at(0),
+    formalRadioButton: () => wrapper.findAll({ name: 'KRadioButton' }).at(1),
+    personalRadioButton: () => wrapper.findAll({ name: 'KRadioButton' }).at(2),
+    nonFormalTextbox: () => wrapper.findAll({ name: 'FacilityNameTextbox' }).at(0),
+    formalTextbox: () => wrapper.findAll({ name: 'FacilityNameTextbox' }).at(1),
+  }
+  const actions = {
+    simulateSubmit: () => wrapper.find({ name: 'OnboardingForm' }).vm.$emit('submit'),
+    selectPreset: preset => wrapper.setData({ selectedPreset: preset }),
+  };
+  jest.spyOn(wrapper.vm, '$emit');
+  return { wrapper, store, els, actions };
+}
+
+describe('FacilityPermissionsForm', () => {
+  it('"non-formal" option is selected by default and facility name textbox is visible', () => {
+    const { els } = makeWrapper();
+    expect(els.nonFormalRadioButton().vm.isChecked).toEqual(true);
+    expect(els.nonFormalTextbox().isVisible()).toEqual(true);
+  });
+
+  it('selecting "formal" shows facility name textbox', () => {
+    const { els, actions } = makeWrapper();
+    actions.selectPreset('formal');
+    expect(els.nonFormalRadioButton().vm.isChecked).toEqual(false);
+    expect(els.nonFormalTextbox().isVisible()).toEqual(false);
+    expect(els.formalRadioButton().vm.isChecked).toEqual(true);
+    expect(els.formalTextbox().isVisible()).toEqual(true);
+  });
+
+  it('selecting "personal" does not show any textboxes', () => {
+    const { els, actions } = makeWrapper();
+    actions.selectPreset('informal');
+    expect(els.personalRadioButton().vm.isChecked).toEqual(true);
+    expect(els.nonFormalTextbox().isVisible()).toEqual(false);
+    expect(els.formalTextbox().isVisible()).toEqual(false);
+  });
+
+  describe('submitting', () => {
+    function testVuex(store, wrapper, expected) {
+      expect(store.state.onboardingData.facility.name).toEqual(expected.name);
+      expect(store.state.onboardingData.preset).toEqual(expected.preset);
+      expect(wrapper.vm.$emit).toHaveBeenCalledWith('submit');
+    }
+
+    it('does not submit if "non-formal" and facility name is empty', () => {
+      const { els, actions, wrapper } = makeWrapper();
+      els.nonFormalTextbox().setData({ facilityName: '' });
+      els.formalTextbox().setData({ facilityName: 'Unused Name' });
+      actions.simulateSubmit();
+      expect(wrapper.vm.$emit).not.toHaveBeenCalled();
+    });
+
+    it('does not submit if "formal" and facility name is empty', () => {
+      const { els, actions, wrapper } = makeWrapper();
+      actions.selectPreset('formal');
+      els.formalTextbox().setData({ facilityName: '' });
+      els.nonFormalTextbox().setData({ facilityName: 'Unused Name' });
+      actions.simulateSubmit();
+      expect(wrapper.vm.$emit).not.toHaveBeenCalled();
+    });
+
+    it('submitting with "non-formal" updates vuex correctly', () => {
+      const { els, actions, store, wrapper } = makeWrapper();
+      els.nonFormalTextbox().setData({
+        facilityName: 'Non-Formal Facility',
+      });
+      actions.simulateSubmit();
+      testVuex(store, wrapper, {
+        name: 'Non-Formal Facility',
+        preset: 'nonformal',
+      });
+    });
+
+    it('submitting with "formal" updates vuex correctly', () => {
+      const { els, actions, store, wrapper } = makeWrapper();
+      actions.selectPreset('formal');
+      els.formalTextbox().setData({
+        facilityName: 'Formal Facility',
+      });
+      actions.simulateSubmit();
+      testVuex(store, wrapper, {
+        name: 'Formal Facility',
+        preset: 'formal',
+      });
+    });
+
+    it('submitting with "personal" updates vuex correctly', () => {
+      const { actions, store, wrapper } = makeWrapper();
+      actions.selectPreset('informal');
+      actions.simulateSubmit();
+      testVuex(store, wrapper, {
+        name: '',
+        preset: 'informal',
+      });
+    });
+  });
+});
diff --git a/kolibri/plugins/setup_wizard/assets/test/views/GuestAccessForm.spec.js b/kolibri/plugins/setup_wizard/assets/test/views/GuestAccessForm.spec.js
new file mode 100644
--- /dev/null
+++ b/kolibri/plugins/setup_wizard/assets/test/views/GuestAccessForm.spec.js
@@ -0,0 +1,48 @@
+import { mount } from '@vue/test-utils';
+import makeStore from '../makeStore';
+import GuestAccessForm from '../../src/views/onboarding-forms/GuestAccessForm';
+
+function makeWrapper(options) {
+  const store = makeStore();
+  if (options.preset) {
+    store.commit('SET_FACILITY_PRESET', options.preset);
+  }
+  if (options.previousChoice !== undefined) {
+    store.commit('SET_ALLOW_GUEST_ACCESS', options.previousChoice);
+  }
+  const wrapper = mount(GuestAccessForm, {
+    store,
+  });
+  jest.spyOn(wrapper.vm, '$emit');
+
+  return { wrapper, store };
+}
+
+describe('GuestAccessForm', () => {
+  it('has the correct default with "nonformal" preset', () => {
+    const { wrapper } = makeWrapper({ preset: 'nonformal' });
+    expect(wrapper.vm.settingIsEnabled).toEqual(true);
+  });
+
+  it('has the correct default with "formal" preset', () => {
+    const { wrapper } = makeWrapper({ preset: 'formal' });
+    expect(wrapper.vm.settingIsEnabled).toEqual(false);
+  });
+
+  it('has the correct default with "personal" preset', () => {
+    const { wrapper } = makeWrapper({ preset: 'personal' });
+    expect(wrapper.vm.settingIsEnabled).toEqual(true);
+  });
+
+  it('if user has set it in a previous step, it is kept', () => {
+    const { wrapper } = makeWrapper({ preset: 'nonformal', previousChoice: false });
+    expect(wrapper.vm.settingIsEnabled).toEqual(false);
+  });
+
+  it('after clicking submit, the setting in vuex is updated', () => {
+    const { wrapper, store } = makeWrapper({ preset: 'formal' });
+    wrapper.find({ name: 'YesNoForm' }).vm.emitSetting();
+    expect(store.state.onboardingData.settings.allow_guest_access).toEqual(false);
+    expect(wrapper.vm.$emit).toHaveBeenCalledTimes(1);
+  });
+});
diff --git a/kolibri/plugins/setup_wizard/assets/test/views/RequirePasswordForLearnersForm.spec.js b/kolibri/plugins/setup_wizard/assets/test/views/RequirePasswordForLearnersForm.spec.js
new file mode 100644
--- /dev/null
+++ b/kolibri/plugins/setup_wizard/assets/test/views/RequirePasswordForLearnersForm.spec.js
@@ -0,0 +1,48 @@
+import { mount } from '@vue/test-utils';
+import makeStore from '../makeStore';
+import RequirePasswordForLearnersForm from '../../src/views/onboarding-forms/RequirePasswordForLearnersForm';
+
+function makeWrapper(options) {
+  const store = makeStore();
+  if (options.preset) {
+    store.commit('SET_FACILITY_PRESET', options.preset);
+  }
+  if (options.previousChoice !== undefined) {
+    store.commit('SET_LEARNER_CAN_LOGIN_WITH_NO_PASSWORD', options.previousChoice);
+  }
+  const wrapper = mount(RequirePasswordForLearnersForm, {
+    store,
+  });
+  jest.spyOn(wrapper.vm, '$emit');
+
+  return { wrapper, store };
+}
+
+describe('RequirePasswordForLearnersForm', () => {
+  it('has the correct default with "nonformal" preset', () => {
+    const { wrapper } = makeWrapper({ preset: 'nonformal' });
+    expect(wrapper.vm.settingIsEnabled).toEqual(true);
+  });
+
+  it('has the correct default with "formal" preset', () => {
+    const { wrapper } = makeWrapper({ preset: 'formal' });
+    expect(wrapper.vm.settingIsEnabled).toEqual(false);
+  });
+
+  it('has the correct default with "personal" preset', () => {
+    const { wrapper } = makeWrapper({ preset: 'personal' });
+    expect(wrapper.vm.settingIsEnabled).toEqual(true);
+  });
+
+  it('if user has set it in a previous step, it is kept', () => {
+    const { wrapper } = makeWrapper({ preset: 'nonformal', previousChoice: false });
+    expect(wrapper.vm.settingIsEnabled).toEqual(false);
+  });
+
+  it('after clicking submit, the setting in vuex is updated', () => {
+    const { wrapper, store } = makeWrapper({ preset: 'formal' });
+    wrapper.find({ name: 'YesNoForm' }).vm.emitSetting();
+    expect(store.state.onboardingData.settings.learner_can_login_with_no_password).toEqual(false);
+    expect(wrapper.vm.$emit).toHaveBeenCalledTimes(1);
+  });
+});
diff --git a/kolibri/plugins/setup_wizard/assets/test/views/SetupWizardIndex.spec.js b/kolibri/plugins/setup_wizard/assets/test/views/SetupWizardIndex.spec.js
new file mode 100644
--- /dev/null
+++ b/kolibri/plugins/setup_wizard/assets/test/views/SetupWizardIndex.spec.js
@@ -0,0 +1,107 @@
+import { mount } from '@vue/test-utils';
+import makeStore from '../makeStore';
+import SetupWizardIndex from '../../src/views/SetupWizardIndex';
+
+function makeWrapper() {
+  const store = makeStore();
+  const wrapper = mount(SetupWizardIndex, {
+    store,
+  });
+  jest.spyOn(wrapper.vm, 'provisionDevice').mockResolvedValue();
+  const els = {
+    DefaultLanguageForm: () => wrapper.find({ name: 'DefaultLanguageForm' }),
+    FacilityPermissionsForm: () => wrapper.find({ name: 'FacilityPermissionsForm' }),
+    GuestAccessForm: () => wrapper.find({ name: 'GuestAccessForm' }),
+    CreateLearnerAccountForm: () => wrapper.find({ name: 'CreateLearnerAccountForm' }),
+    RequirePasswordForLearnersForm: () => wrapper.find({ name: 'RequirePasswordForLearnersForm' }),
+    SuperuserCredentialsForm: () => wrapper.find({ name: 'SuperuserCredentialsForm' }),
+  };
+  return { wrapper, store, els };
+}
+
+describe('SetupWizardIndex', () => {
+  it('clicking next takes you to the next step', async () => {
+    const { els, wrapper } = makeWrapper();
+    // Step 1: DefaultLanguageForm
+    expect(els.DefaultLanguageForm().isVueComponent).toBe(true);
+    // Simulate clicking submit because clicking the button doesn't work
+    els.DefaultLanguageForm().vm.$emit('submit');
+    expect(els.DefaultLanguageForm().exists()).toBe(false);
+
+    // Step 2: Facility Permissions Form
+    expect(els.FacilityPermissionsForm().isVueComponent).toBe(true);
+    els.FacilityPermissionsForm().vm.$emit('submit');
+    await wrapper.vm.$nextTick();
+    expect(els.FacilityPermissionsForm().exists()).toBe(false);
+
+    // Step 3: Guest Access Form
+    expect(els.GuestAccessForm().isVueComponent).toBe(true);
+    els.GuestAccessForm().vm.$emit('submit');
+    expect(els.GuestAccessForm().exists()).toBe(false);
+
+    // Step 4: Learners can create own account form
+    expect(els.CreateLearnerAccountForm().isVueComponent).toBe(true);
+    els.CreateLearnerAccountForm().vm.$emit('submit');
+    expect(els.CreateLearnerAccountForm().exists()).toBe(false);
+
+    // Step 5: Require password for learners form
+    expect(els.RequirePasswordForLearnersForm().isVueComponent).toBe(true);
+    els.RequirePasswordForLearnersForm().vm.$emit('submit');
+    expect(els.RequirePasswordForLearnersForm().exists()).toBe(false);
+
+    // Step 6: Superuser Credentials Form
+    expect(els.SuperuserCredentialsForm().isVueComponent).toBe(true);
+    els.SuperuserCredentialsForm().vm.$emit('submit');
+    expect(wrapper.vm.provisionDevice).toHaveBeenCalledTimes(1);
+  });
+
+  it('submits a default facility name if "personal" preset is used', () => {
+    const { els, wrapper, store } = makeWrapper();
+    // set superuser, since that's how name is derived
+    store.commit('SET_FACILITY_PRESET', 'personal');
+    store.commit('SET_SU', {
+      name: 'Fred Rogers',
+      username: 'mr_rogers',
+      password: 'password',
+    });
+    store.commit('SET_ONBOARDING_STEP', 6);
+    els.SuperuserCredentialsForm().vm.$emit('submit');
+    const matcher = expect.objectContaining({ facility: { name: 'Home Facility Fred Rogers' } });
+    expect(wrapper.vm.provisionDevice).toHaveBeenCalledWith(matcher);
+  });
+
+  it('submits correct data when provisioning', () => {
+    const { els, wrapper, store } = makeWrapper();
+    // set superuser, since that's how name is derived
+    store.commit('SET_FACILITY_PRESET', 'formal');
+    store.commit('SET_SU', {
+      name: 'Fred Rogers',
+      username: 'mr_rogers',
+      password: 'password',
+    });
+    store.commit('SET_FACILITY_NAME', "Mr. Roger's Neighborhood");
+    store.commit('SET_ALLOW_GUEST_ACCESS', true);
+    store.commit('SET_LEARNER_CAN_SIGN_UP', false);
+    store.commit('SET_LEARNER_CAN_LOGIN_WITH_NO_PASSWORD', true);
+    store.commit('SET_ONBOARDING_STEP', 6);
+    els.SuperuserCredentialsForm().vm.$emit('submit');
+    const matcher = expect.objectContaining({
+      language_id: 'en',
+      facility: { name: "Mr. Roger's Neighborhood" },
+      preset: 'formal',
+      superuser: {
+        full_name: 'Fred Rogers',
+        username: 'mr_rogers',
+        password: 'password',
+      },
+      settings: {
+        allow_guest_access: true,
+        learner_can_sign_up: false,
+        learner_can_edit_name: false,
+        learner_can_edit_username: false,
+        learner_can_login_with_no_password: true,
+      },
+    });
+    expect(wrapper.vm.provisionDevice).toHaveBeenCalledWith(matcher);
+  });
+});
diff --git a/kolibri/plugins/setup_wizard/assets/test/views/SuperuserCredentialsForm.spec.js b/kolibri/plugins/setup_wizard/assets/test/views/SuperuserCredentialsForm.spec.js
new file mode 100644
--- /dev/null
+++ b/kolibri/plugins/setup_wizard/assets/test/views/SuperuserCredentialsForm.spec.js
@@ -0,0 +1,40 @@
+import { mount } from '@vue/test-utils';
+import makeStore from '../makeStore';
+import SuperuserCredentialsForm from '../../src/views/onboarding-forms/SuperuserCredentialsForm';
+
+function makeWrapper() {
+  const store = makeStore();
+  const wrapper = mount(SuperuserCredentialsForm, {
+    store,
+  });
+  jest.spyOn(wrapper.vm, '$emit');
+  const actions = {
+    simulateSubmit: () => wrapper.find({ name: 'OnboardingForm' }).vm.$emit('submit'),
+  };
+  return { store, wrapper, actions };
+}
+
+describe('SuperuserCredentialsForm', () => {
+  it('clicking submit updates vuex with correct data', () => {
+    const { store, wrapper, actions } = makeWrapper();
+    wrapper.setData({
+      name: 'Schoolhouse Rock',
+      username: 'schoolhouse_rock',
+      password: 'password',
+      passwordConfirm: 'password',
+      visitedFields: {
+        name: true,
+        username: true,
+        password: true,
+        passwordConfirm: true,
+      },
+    });
+    actions.simulateSubmit();
+    expect(store.state.onboardingData.superuser).toEqual({
+      full_name: 'Schoolhouse Rock',
+      username: 'schoolhouse_rock',
+      password: 'password',
+    });
+    expect(wrapper.vm.$emit).toHaveBeenCalledWith('submit');
+  });
+});