hippo-fw 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/Gemfile +16 -11
  4. data/Rakefile +0 -7
  5. data/bin/hippo +5 -1
  6. data/client/hippo/__mocks__/config.js +2 -3
  7. data/client/hippo/boot.jsx +0 -2
  8. data/client/hippo/components/asset.jsx +1 -1
  9. data/client/hippo/components/form.jsx +8 -4
  10. data/client/hippo/components/form/fields.jsx +28 -14
  11. data/client/hippo/components/form/model.js +65 -20
  12. data/client/hippo/components/form/wrapper.jsx +11 -5
  13. data/client/hippo/components/icon.jsx +1 -1
  14. data/client/hippo/components/master-detail.jsx +66 -0
  15. data/client/hippo/components/master-detail.scss +50 -0
  16. data/client/hippo/components/record-finder.jsx +5 -5
  17. data/client/hippo/components/tool-tip.jsx +20 -0
  18. data/client/hippo/config.js +5 -3
  19. data/client/hippo/extensions/base.js +4 -0
  20. data/client/hippo/lib/smooth-scroll.js +17 -16
  21. data/client/hippo/models/asset.js +8 -10
  22. data/client/hippo/models/collection.js +1 -4
  23. data/client/hippo/models/query/array-result.js +11 -9
  24. data/client/hippo/models/sync.js +3 -3
  25. data/client/hippo/models/tenant.js +29 -0
  26. data/client/hippo/screens/system-settings.jsx +5 -4
  27. data/client/hippo/screens/system-settings/mailer-config.jsx +11 -17
  28. data/client/hippo/screens/system-settings/tenant.jsx +90 -0
  29. data/client/hippo/screens/user-management/edit-form.jsx +15 -25
  30. data/client/hippo/testing/index.js +1 -0
  31. data/client/hippo/workspace/styles.scss +0 -23
  32. data/command-reference-files/initial/.babelrc +10 -8
  33. data/command-reference-files/initial/Gemfile +1 -1
  34. data/{views/index.html → command-reference-files/initial/views/index.erb} +1 -0
  35. data/config/routes.rb +48 -17
  36. data/config/webpack.config.js +7 -12
  37. data/db/migrate/01_create_tenants.rb +13 -0
  38. data/db/migrate/{01_create_system_settings.rb → 02_create_system_settings.rb} +2 -1
  39. data/db/migrate/{02_create_assets.rb → 03_create_assets.rb} +2 -4
  40. data/db/migrate/{20140615031600_create_users.rb → 04_create_users.rb} +4 -2
  41. data/db/seed.rb +10 -1
  42. data/hippo-fw.gemspec +53 -51
  43. data/lib/hippo.rb +7 -1
  44. data/lib/hippo/access.rb +0 -1
  45. data/lib/hippo/access/roles/basic_user.rb +2 -0
  46. data/lib/hippo/api.rb +4 -3
  47. data/lib/hippo/{access → api}/authentication_provider.rb +3 -1
  48. data/lib/hippo/api/controller_base.rb +2 -2
  49. data/lib/hippo/api/handlers/asset.rb +28 -2
  50. data/lib/hippo/api/handlers/tenant.rb +26 -0
  51. data/lib/hippo/api/helper_methods.rb +5 -13
  52. data/lib/hippo/api/request_wrapper.rb +8 -1
  53. data/lib/hippo/api/root.rb +50 -51
  54. data/lib/hippo/api/route_set.rb +101 -0
  55. data/lib/hippo/api/routing.rb +9 -98
  56. data/lib/hippo/api/tenant_domain_router.rb +21 -0
  57. data/lib/hippo/asset.rb +1 -0
  58. data/lib/hippo/command.rb +1 -24
  59. data/lib/hippo/command/app.rb +4 -3
  60. data/lib/hippo/command/console.rb +7 -0
  61. data/lib/hippo/command/generate.rb +0 -5
  62. data/lib/hippo/command/guard.rb +1 -0
  63. data/lib/hippo/command/jest.rb +2 -2
  64. data/lib/hippo/command/server.rb +1 -3
  65. data/lib/hippo/command/webpack.rb +6 -26
  66. data/lib/hippo/configuration.rb +21 -13
  67. data/lib/hippo/db.rb +2 -0
  68. data/lib/hippo/db/migrations.rb +9 -2
  69. data/lib/hippo/extension.rb +49 -14
  70. data/lib/hippo/extension/definition.rb +0 -4
  71. data/lib/hippo/guard_tasks.rb +3 -11
  72. data/lib/hippo/mailer.rb +28 -16
  73. data/lib/hippo/model.rb +10 -0
  74. data/lib/hippo/numbers.rb +1 -1
  75. data/lib/hippo/rake_tasks.rb +7 -1
  76. data/lib/hippo/spec_helper.rb +33 -11
  77. data/lib/hippo/system_settings.rb +1 -0
  78. data/lib/hippo/templates/base.rb +1 -1
  79. data/lib/hippo/templates/mail.rb +26 -0
  80. data/lib/hippo/templates/tenant_change.rb +23 -0
  81. data/lib/hippo/tenant.rb +53 -0
  82. data/lib/hippo/user.rb +12 -6
  83. data/lib/hippo/version.rb +1 -1
  84. data/lib/hippo/webpack.rb +57 -0
  85. data/lib/hippo/{command → webpack}/client_config.rb +7 -21
  86. data/package.json +3 -3
  87. data/spec/client/components/__snapshots__/master-detail.spec.jsx.snap +22 -0
  88. data/spec/client/components/form.spec.jsx +14 -14
  89. data/spec/client/components/master-detail.spec.jsx +24 -0
  90. data/spec/client/components/record-finder.spec.jsx +5 -2
  91. data/spec/client/models/asset.spec.js +2 -13
  92. data/spec/client/models/base.spec.js +1 -11
  93. data/spec/client/models/query.spec.js +2 -4
  94. data/spec/client/models/sync.spec.js +7 -0
  95. data/spec/client/screens/__snapshots__/system-settings.spec.jsx.snap +79 -0
  96. data/spec/client/screens/system-settings-tenants.spec.jsx +18 -0
  97. data/spec/client/workspace/__snapshots__/menu.spec.jsx.snap +29 -313
  98. data/spec/client/workspace/menu.spec.jsx +1 -9
  99. data/spec/factories/tenant.rb +13 -0
  100. data/spec/fixtures/mail/test_email.liquid +1 -0
  101. data/spec/fixtures/{test_printer.tex → test_printer.tex.erb} +0 -0
  102. data/spec/server/api/controller_base_spec.rb +1 -1
  103. data/spec/server/api/tenant_change_spec.rb +24 -0
  104. data/spec/server/api/tenant_isolation_spec.rb +37 -0
  105. data/spec/server/asset_spec.rb +6 -6
  106. data/spec/server/command_spec.rb +0 -5
  107. data/spec/server/mailer_spec.rb +25 -23
  108. data/spec/server/numbers_spec.rb +12 -13
  109. data/spec/server/print/form_spec.rb +2 -1
  110. data/spec/server/strings_spec.rb +13 -13
  111. data/templates/.babelrc +10 -8
  112. data/templates/js/screen-definitions.js +8 -10
  113. data/templates/mail/tenant_change.liquid +13 -0
  114. data/{command-reference-files/initial/views/index.html → views/index.erb} +5 -2
  115. data/yarn.lock +22 -169
  116. metadata +56 -30
  117. data/client/hippo/components/form/field-prop-type.js +0 -16
  118. data/lib/hippo/api/default_routes.rb +0 -38
  119. data/lib/hippo/command/generate_component.rb +0 -28
  120. data/lib/hippo/command/generate_component.usage +0 -11
  121. data/lib/hippo/command/webpack_view.rb +0 -32
  122. data/lib/hippo/multi_server_boot.rb +0 -26
  123. data/lib/hippo/reloadable_view.rb +0 -13
  124. data/templates/client/components/.gitkeep +0 -0
  125. data/templates/client/components/BaseComponent.coffee +0 -9
  126. data/templates/client/components/Component.cjsx +0 -4
  127. data/templates/client/components/template.html +0 -3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hippo-fw",
3
- "version": "0.7.0",
3
+ "version": "0.9.1",
4
4
  "description": "Hippo is a framework for easily writing single page web applications",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/argosity/hippo",
@@ -33,12 +33,11 @@
33
33
  "domtastic": "^0.12.3",
34
34
  "enzyme": "^2.8.2",
35
35
  "eslint": "^3.13.1",
36
- "eslint-config-argosity": "1.0.5",
36
+ "eslint-config-argosity": "^1.1.3",
37
37
  "file-loader": "^0.11.1",
38
38
  "flexboxgrid": "^6.3.1",
39
39
  "grommet": "^1.4.0",
40
40
  "history": "3.2.1",
41
- "html-webpack-plugin": "^2.28.0",
42
41
  "identity-obj-proxy": "^3.0.0",
43
42
  "invariant": "^2.2.2",
44
43
  "jest": "^19.0.2",
@@ -67,6 +66,7 @@
67
66
  "react-router": "^3.0.2",
68
67
  "react-sidebar": "^2.3.0",
69
68
  "react-test-renderer": "^15.4.2",
69
+ "react-tippy": "^0.14.0",
70
70
  "react-virtualized": "^9.0.0",
71
71
  "rsvp": "^3.3.3",
72
72
  "sass-loader": "^4.1.1",
@@ -0,0 +1,22 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`Master Detail Component renders and matches snapshot 1`] = `
4
+ <div
5
+ className="master-detail-wrapper has-detail"
6
+ >
7
+ <div
8
+ className="master"
9
+ >
10
+ <h1>
11
+ Hi, i am master
12
+ </h1>
13
+ </div>
14
+ <div
15
+ className="detail"
16
+ >
17
+ <h1>
18
+ Hi, i am detail
19
+ </h1>
20
+ </div>
21
+ </div>
22
+ `;
@@ -1,24 +1,23 @@
1
1
  import React from 'react';
2
2
  import {
3
- nonBlank, validEmail, hasLength,
4
- FormFields, FormFieldPropType,
3
+ nonBlank, validEmail, hasLength, validURL, FormState,
5
4
  } from 'hippo/components/form';
6
5
 
7
6
  describe('Form Validation functions', () => {
8
7
  it('validates fields', () => {
9
- const form = new FormFields({
10
- name: hasLength({ length: 3, default: 'onee' }),
11
- email: validEmail,
8
+ const form = new FormState();
9
+ form.setFields({
10
+ email: { validate: validEmail },
11
+ name: { validate: hasLength({ length: 3 }) },
12
12
  });
13
+ expect(form.get('email.isValid')).toBe(false);
13
14
  expect(form.isValid).toBe(false);
14
15
  expect(form.isTouched).toBe(false);
15
16
 
16
- expect(form.get('email').isValid).toBe(false);
17
+ expect(form.get('email.isValid')).toBe(false);
17
18
  form.get('email').value = 'test@test.com';
18
19
  expect(form.get('email').isValid).toBe(true);
19
20
 
20
- expect(form.isValid).toBe(true);
21
-
22
21
  form.fields.get('email').isTouched = true;
23
22
  expect(form.isTouched).toBe(true);
24
23
  form.fields.get('name').value = '';
@@ -47,17 +46,18 @@ describe('Form Validation functions', () => {
47
46
  expect(test.test('@test.com')).toBe(false);
48
47
  expect(test.test('test@test.com')).toBe(true);
49
48
  });
49
+
50
50
  it('tests hasLength', () => {
51
51
  const len = hasLength(3);
52
52
  expect(len.test('a')).toBe(false);
53
53
  expect(len.test('abc')).toBe(true);
54
54
  });
55
55
 
56
- it('validates prop types', () => {
57
- const form = new FormFields({ name: nonBlank });
58
- const validator = FormFieldPropType;
59
- expect(validator({ name: 'name' })).toBeInstanceOf(Error);
60
- expect(validator({ name: 'no-field', form })).toBeInstanceOf(Error);
61
- expect(validator({ name: 'name', form })).toBeNull();
56
+ it('tests url', () => {
57
+ let url = validURL();
58
+ expect(url.test('bad-url')).toBe(false);
59
+ expect(url.test('')).toBe(false);
60
+ url = validURL({ allowBlank: true });
61
+ expect(url.test('')).toBe(true);
62
62
  });
63
63
  });
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+
3
+ import { Snapshot } from 'hippo/testing/screens';
4
+ import MasterDetail from 'hippo/components/master-detail';
5
+
6
+ function MasterComp() { return <h1>Hi, i am master</h1>; }
7
+ function DetailComp() { return <h1>Hi, i am detail</h1>; }
8
+
9
+ describe('Master Detail Component', () => {
10
+ it('renders and matches snapshot', () => {
11
+ expect(Snapshot(
12
+ <MasterDetail
13
+ master={<MasterComp />}
14
+ detail={<DetailComp />}
15
+ />)).toMatchSnapshot();
16
+ });
17
+
18
+ it('renders and then can add detail', () => {
19
+ const md = shallow(<MasterDetail master={<MasterComp />} />);
20
+ expect(md).not.toHaveRendered('.has-detail');
21
+ md.setProps({ detail: <DetailComp /> });
22
+ expect(md).toHaveRendered('.has-detail');
23
+ });
24
+ });
@@ -1,7 +1,6 @@
1
1
  import React from 'react';
2
2
  import { range } from 'lodash';
3
3
 
4
-
5
4
  import Query from 'hippo/models/query';
6
5
 
7
6
  import { Snapshot } from 'hippo/testing/screens';
@@ -10,11 +9,15 @@ import RecordFinder from 'hippo/components/record-finder';
10
9
  import { Container } from '../test-models';
11
10
 
12
11
  jest.mock('hippo/models/sync');
12
+ import { FormState, stringValue } from 'hippo/components/form';
13
13
 
14
14
  describe("RecordFinder Component", () => {
15
15
  let query;
16
16
  let props;
17
+ let formState;
18
+
17
19
  beforeEach(() => {
20
+ formState = new FormState();
18
21
  query = new Query({
19
22
  src: Container,
20
23
  fields: [
@@ -28,7 +31,7 @@ describe("RecordFinder Component", () => {
28
31
  onRecordFound: jest.fn(),
29
32
  name: 'name',
30
33
  recordsTitle: 'Foos',
31
- fields: { name: { value: 'ONE', events: {} } },
34
+ formState,
32
35
  query,
33
36
  };
34
37
  range(0, 5).forEach(
@@ -23,7 +23,7 @@ describe('Asset Test', () => {
23
23
  const image = TestImage.deserialize({ asset: LogoJson });
24
24
  expect(image.asset.isImage).toBe(true);
25
25
  expect(image.asset.previewUrl)
26
- .toEqual(`/api/asset/${LogoJson.file_data.thumbnail.id}`);
26
+ .toContain(LogoJson.file_data.thumbnail.id);
27
27
  expect(image.asset.owner).toBe(image);
28
28
  expect(image.asset.owner_association_name).toBe('asset');
29
29
  });
@@ -32,19 +32,8 @@ describe('Asset Test', () => {
32
32
  model.asset.file = file;
33
33
  expect(model.asset.isDirty).toBe(true);
34
34
  model.asset.save();
35
- expect(fetch).lastCalledWith('/api/asset', expect.objectContaining({
35
+ expect(fetch).lastCalledWith(expect.anything(), expect.objectContaining({
36
36
  body: expect.any(FormData),
37
37
  }));
38
38
  });
39
-
40
- it('attempts to save after owner does', () => {
41
- model.asset.save = jest.fn();
42
- expect(model.asset.isDirty).toBe(false);
43
- model.save();
44
- expect(model.asset.save).not.toHaveBeenCalled();
45
- model.asset.file = file;
46
- expect(model.asset.isDirty).toBe(true);
47
- model.save();
48
- expect(model.asset.save).toHaveBeenCalled();
49
- });
50
39
  });
@@ -29,7 +29,7 @@ describe('BaseModel Test', () => {
29
29
  autorun(() => spy(box.syncUrl));
30
30
  expect(spy).toHaveBeenCalledWith('/api/test/boxes/11');
31
31
  expect(box.id).toEqual(11);
32
- expect(box.identifier).toEqual(11);
32
+ expect(box.identifierFieldValue).toEqual(11);
33
33
  expect(box.isNew).toEqual(false);
34
34
  expect(box.syncUrl).toEqual('/api/test/boxes/11');
35
35
  box.id = 42;
@@ -63,16 +63,6 @@ describe('BaseModel Test', () => {
63
63
  expect(box.bad).toBeUndefined();
64
64
  });
65
65
 
66
- it('re-fetches record', () => {
67
- const box = new Box({ id: 1 });
68
- expect(box.isNew).toBe(false);
69
- box.fetch();
70
- expect(fetch).lastCalledWith(
71
- '/api/test/boxes/1.json?l=1&q%5Bid%5D=1',
72
- { headers: { 'Content-Type': 'application/json' }, method: 'GET' },
73
- );
74
- });
75
-
76
66
  it('validates and records "type" in schema', () => {
77
67
  const box = new Box();
78
68
  box.id = 23;
@@ -1,6 +1,4 @@
1
- import Config from 'hippo/config';
2
1
  import Sync from 'hippo/models/sync';
3
-
4
2
  import { map, find, range, shuffle } from 'lodash';
5
3
 
6
4
  import Clause from 'hippo/models/query/clause';
@@ -86,8 +84,8 @@ describe('Model Queries', () => {
86
84
  expect(map(clause.validOperators, 'id')).toEqual(['eq', 'lt', 'gt']);
87
85
  });
88
86
 
89
- it('can read visible identifier field', () => {
90
- expect(query.info.visibleIdentifierField.id).toEqual('id');
87
+ it('can read identifier field', () => {
88
+ expect(query.info.identifierField.id).toEqual('id');
91
89
  });
92
90
 
93
91
  it('converts a row to object', () => {
@@ -1,7 +1,14 @@
1
1
  import Sync from 'hippo/models/sync';
2
+ import Config from 'hippo/config';
2
3
  import { Box } from '../test-models';
3
4
 
5
+ jest.unmock('hippo/models/sync');
6
+
4
7
  describe('Network sync', () => {
8
+ beforeEach(() => {
9
+ Config.api_host = '';
10
+ });
11
+
5
12
  it('makes a request', () => {
6
13
  fetch.mockResponseOnce(JSON.stringify({ access_token: '12345' }));
7
14
  Sync.perform('/foo')
@@ -77,6 +77,85 @@ exports[`SystemSettings Screen renders 1`] = `
77
77
  >
78
78
  title
79
79
  </h1>
80
+ <div
81
+ className="tenant-edit-form"
82
+ >
83
+ <h3
84
+ className="grommetux-heading"
85
+ >
86
+ Account
87
+ </h3>
88
+ <div
89
+ className="row"
90
+ >
91
+ <div
92
+ className="form-field col-md-4 col-xs-6"
93
+ >
94
+ <div
95
+ className="grommetux-form-field grommetux-form-field--size-medium"
96
+ onClick={[Function]}
97
+ >
98
+ <label
99
+ className="grommetux-form-field__label"
100
+ htmlFor={undefined}
101
+ >
102
+ Identifier
103
+ </label>
104
+ <span
105
+ className="grommetux-form-field__contents"
106
+ >
107
+ <input
108
+ autoComplete="off"
109
+ autoFocus={undefined}
110
+ className="grommetux-text-input grommetux-input"
111
+ defaultValue={undefined}
112
+ name="slug"
113
+ onBlur={[Function]}
114
+ onChange={[Function]}
115
+ onFocus={[Function]}
116
+ onKeyDown={[Function]}
117
+ placeholder={undefined}
118
+ type="text"
119
+ value=""
120
+ />
121
+ </span>
122
+ </div>
123
+ </div>
124
+ <div
125
+ className="form-field col-md-4 col-xs-6"
126
+ >
127
+ <div
128
+ className="grommetux-form-field grommetux-form-field--size-medium"
129
+ onClick={[Function]}
130
+ >
131
+ <label
132
+ className="grommetux-form-field__label"
133
+ htmlFor={undefined}
134
+ >
135
+ Name
136
+ </label>
137
+ <span
138
+ className="grommetux-form-field__contents"
139
+ >
140
+ <input
141
+ autoComplete="off"
142
+ autoFocus={undefined}
143
+ className="grommetux-text-input grommetux-input"
144
+ defaultValue={undefined}
145
+ name="name"
146
+ onBlur={[Function]}
147
+ onChange={[Function]}
148
+ onFocus={[Function]}
149
+ onKeyDown={[Function]}
150
+ placeholder={undefined}
151
+ type="text"
152
+ value=""
153
+ />
154
+ </span>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </div>
80
159
  <h3
81
160
  className="grommetux-heading"
82
161
  >
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+
3
+ import TenantModel from 'hippo/models/tenant';
4
+ import Tenant from 'hippo/screens/system-settings/tenant';
5
+
6
+ describe('SystemSettings Tenants section', () => {
7
+ it('displays warning dialog after update slug', (done) => {
8
+ const t = mount(<Tenant />);
9
+ expect(t).not.toHaveRendered('TenantSlugChange Layer');
10
+ TenantModel.current.slug = 'ONE';
11
+ t.find('input[name="name"]').simulate('change', { target: { value: 'NEW' } });
12
+ t.instance().onSave();
13
+ setTimeout(() => {
14
+ expect(t).toHaveRendered('TenantSlugChange Layer');
15
+ done();
16
+ });
17
+ });
18
+ });
@@ -28,15 +28,41 @@ exports[`Workspace Menu has groups with screens 1`] = `
28
28
  <h4
29
29
  className="grommetux-heading grommetux-heading--strong"
30
30
  >
31
- Group # 0
31
+ Settings
32
32
  <i
33
- className="icon icon-grp-0"
33
+ className="icon icon-wrench"
34
34
  onClick={undefined}
35
35
  style={undefined}
36
36
  />
37
37
  </h4>
38
38
  </header>
39
39
  </div>
40
+ <a
41
+ aria-label={undefined}
42
+ className="grommetux-anchor"
43
+ href={undefined}
44
+ onClick={[Function]}
45
+ >
46
+ User Management
47
+ <i
48
+ className="icon icon-group"
49
+ onClick={undefined}
50
+ style={undefined}
51
+ />
52
+ </a>
53
+ <a
54
+ aria-label={undefined}
55
+ className="grommetux-anchor"
56
+ href={undefined}
57
+ onClick={[Function]}
58
+ >
59
+ System Settings
60
+ <i
61
+ className="icon icon-cogs"
62
+ onClick={undefined}
63
+ style={undefined}
64
+ />
65
+ </a>
40
66
  <a
41
67
  aria-label={undefined}
42
68
  className="grommetux-anchor"
@@ -53,7 +79,7 @@ exports[`Workspace Menu has groups with screens 1`] = `
53
79
  </nav>
54
80
  `;
55
81
 
56
- exports[`Workspace Menu has renders a menu option 1`] = `
82
+ exports[`Workspace Menu has renders menu options 1`] = `
57
83
  <a
58
84
  aria-label={undefined}
59
85
  className="grommetux-anchor"
@@ -68,313 +94,3 @@ exports[`Workspace Menu has renders a menu option 1`] = `
68
94
  />
69
95
  </a>
70
96
  `;
71
-
72
- exports[`Workspace Menu renders groups 1`] = `
73
- <div
74
- className="grommetux-box grommetux-box--direction-column grommetux-box--responsive grommetux-box--pad-none grommetux-box--separator-right grommetux-background-color-index-brand grommetux-background-color-index--pending grommetux-sidebar grommetux-sidebar--full grommetux-sidebar--small"
75
- id={undefined}
76
- onClick={undefined}
77
- role={undefined}
78
- style={Object {}}
79
- tabIndex={undefined}
80
- >
81
- <div
82
- className="grommetux-box grommetux-box--direction-column grommetux-box--responsive grommetux-box--pad-none grommetux-box--flex-off"
83
- id={undefined}
84
- onClick={undefined}
85
- role={undefined}
86
- style={Object {}}
87
- tabIndex={undefined}
88
- >
89
- <header
90
- className="grommetux-box grommetux-box--direction-row grommetux-box--justify-between grommetux-box--align-center grommetux-box--pad-horizontal-medium grommetux-header grommetux-header--large"
91
- id={undefined}
92
- onClick={undefined}
93
- role={undefined}
94
- style={Object {}}
95
- tabIndex={undefined}
96
- >
97
- Logo
98
- </header>
99
- </div>
100
- <nav
101
- className="grommetux-box grommetux-box--direction-column grommetux-box--justify-between grommetux-box--align-start grommetux-box--responsive grommetux-box--pad-none grommetux-menu grommetux-menu--column grommetux-menu--primary grommetux-menu--inline"
102
- id={undefined}
103
- onClick={undefined}
104
- role={undefined}
105
- style={Object {}}
106
- tabIndex={undefined}
107
- >
108
- <div
109
- className="grommetux-box grommetux-box--direction-column grommetux-box--responsive grommetux-box--pad-none grommetux-box--flex-off"
110
- id={undefined}
111
- onClick={undefined}
112
- role={undefined}
113
- style={Object {}}
114
- tabIndex={undefined}
115
- >
116
- <header
117
- className="grommetux-box grommetux-box--direction-row grommetux-box--align-end grommetux-box--pad-horizontal-medium grommetux-header"
118
- id={undefined}
119
- onClick={undefined}
120
- role={undefined}
121
- style={Object {}}
122
- tabIndex={undefined}
123
- >
124
- <h4
125
- className="grommetux-heading grommetux-heading--strong"
126
- >
127
- Group # 0
128
- <i
129
- className="icon icon-grp-0"
130
- onClick={undefined}
131
- style={undefined}
132
- />
133
- </h4>
134
- </header>
135
- </div>
136
- <a
137
- aria-label={undefined}
138
- className="grommetux-anchor"
139
- href={undefined}
140
- onClick={[Function]}
141
- >
142
- test screen for 0
143
- <i
144
- className="icon icon-unknown"
145
- onClick={undefined}
146
- style={undefined}
147
- />
148
- </a>
149
- </nav>
150
- <nav
151
- className="grommetux-box grommetux-box--direction-column grommetux-box--justify-between grommetux-box--align-start grommetux-box--responsive grommetux-box--pad-none grommetux-menu grommetux-menu--column grommetux-menu--primary grommetux-menu--inline"
152
- id={undefined}
153
- onClick={undefined}
154
- role={undefined}
155
- style={Object {}}
156
- tabIndex={undefined}
157
- >
158
- <div
159
- className="grommetux-box grommetux-box--direction-column grommetux-box--responsive grommetux-box--pad-none grommetux-box--flex-off"
160
- id={undefined}
161
- onClick={undefined}
162
- role={undefined}
163
- style={Object {}}
164
- tabIndex={undefined}
165
- >
166
- <header
167
- className="grommetux-box grommetux-box--direction-row grommetux-box--align-end grommetux-box--pad-horizontal-medium grommetux-header"
168
- id={undefined}
169
- onClick={undefined}
170
- role={undefined}
171
- style={Object {}}
172
- tabIndex={undefined}
173
- >
174
- <h4
175
- className="grommetux-heading grommetux-heading--strong"
176
- >
177
- Group # 1
178
- <i
179
- className="icon icon-grp-1"
180
- onClick={undefined}
181
- style={undefined}
182
- />
183
- </h4>
184
- </header>
185
- </div>
186
- <a
187
- aria-label={undefined}
188
- className="grommetux-anchor"
189
- href={undefined}
190
- onClick={[Function]}
191
- >
192
- test screen for 1
193
- <i
194
- className="icon icon-unknown"
195
- onClick={undefined}
196
- style={undefined}
197
- />
198
- </a>
199
- </nav>
200
- <nav
201
- className="grommetux-box grommetux-box--direction-column grommetux-box--justify-between grommetux-box--align-start grommetux-box--responsive grommetux-box--pad-none grommetux-menu grommetux-menu--column grommetux-menu--primary grommetux-menu--inline"
202
- id={undefined}
203
- onClick={undefined}
204
- role={undefined}
205
- style={Object {}}
206
- tabIndex={undefined}
207
- >
208
- <div
209
- className="grommetux-box grommetux-box--direction-column grommetux-box--responsive grommetux-box--pad-none grommetux-box--flex-off"
210
- id={undefined}
211
- onClick={undefined}
212
- role={undefined}
213
- style={Object {}}
214
- tabIndex={undefined}
215
- >
216
- <header
217
- className="grommetux-box grommetux-box--direction-row grommetux-box--align-end grommetux-box--pad-horizontal-medium grommetux-header"
218
- id={undefined}
219
- onClick={undefined}
220
- role={undefined}
221
- style={Object {}}
222
- tabIndex={undefined}
223
- >
224
- <h4
225
- className="grommetux-heading grommetux-heading--strong"
226
- >
227
- Group # 2
228
- <i
229
- className="icon icon-grp-2"
230
- onClick={undefined}
231
- style={undefined}
232
- />
233
- </h4>
234
- </header>
235
- </div>
236
- <a
237
- aria-label={undefined}
238
- className="grommetux-anchor"
239
- href={undefined}
240
- onClick={[Function]}
241
- >
242
- test screen for 2
243
- <i
244
- className="icon icon-unknown"
245
- onClick={undefined}
246
- style={undefined}
247
- />
248
- </a>
249
- </nav>
250
- <nav
251
- className="grommetux-box grommetux-box--direction-column grommetux-box--justify-between grommetux-box--align-start grommetux-box--responsive grommetux-box--pad-none grommetux-menu grommetux-menu--column grommetux-menu--primary grommetux-menu--inline"
252
- id={undefined}
253
- onClick={undefined}
254
- role={undefined}
255
- style={Object {}}
256
- tabIndex={undefined}
257
- >
258
- <div
259
- className="grommetux-box grommetux-box--direction-column grommetux-box--responsive grommetux-box--pad-none grommetux-box--flex-off"
260
- id={undefined}
261
- onClick={undefined}
262
- role={undefined}
263
- style={Object {}}
264
- tabIndex={undefined}
265
- >
266
- <header
267
- className="grommetux-box grommetux-box--direction-row grommetux-box--align-end grommetux-box--pad-horizontal-medium grommetux-header"
268
- id={undefined}
269
- onClick={undefined}
270
- role={undefined}
271
- style={Object {}}
272
- tabIndex={undefined}
273
- >
274
- <h4
275
- className="grommetux-heading grommetux-heading--strong"
276
- >
277
- Group # 3
278
- <i
279
- className="icon icon-grp-3"
280
- onClick={undefined}
281
- style={undefined}
282
- />
283
- </h4>
284
- </header>
285
- </div>
286
- <a
287
- aria-label={undefined}
288
- className="grommetux-anchor"
289
- href={undefined}
290
- onClick={[Function]}
291
- >
292
- test screen for 3
293
- <i
294
- className="icon icon-unknown"
295
- onClick={undefined}
296
- style={undefined}
297
- />
298
- </a>
299
- </nav>
300
- <nav
301
- className="grommetux-box grommetux-box--direction-column grommetux-box--justify-between grommetux-box--align-start grommetux-box--responsive grommetux-box--pad-none grommetux-menu grommetux-menu--column grommetux-menu--primary grommetux-menu--inline"
302
- id={undefined}
303
- onClick={undefined}
304
- role={undefined}
305
- style={Object {}}
306
- tabIndex={undefined}
307
- >
308
- <div
309
- className="grommetux-box grommetux-box--direction-column grommetux-box--responsive grommetux-box--pad-none grommetux-box--flex-off"
310
- id={undefined}
311
- onClick={undefined}
312
- role={undefined}
313
- style={Object {}}
314
- tabIndex={undefined}
315
- >
316
- <header
317
- className="grommetux-box grommetux-box--direction-row grommetux-box--align-end grommetux-box--pad-horizontal-medium grommetux-header"
318
- id={undefined}
319
- onClick={undefined}
320
- role={undefined}
321
- style={Object {}}
322
- tabIndex={undefined}
323
- >
324
- <h4
325
- className="grommetux-heading grommetux-heading--strong"
326
- >
327
- Group # 4
328
- <i
329
- className="icon icon-grp-4"
330
- onClick={undefined}
331
- style={undefined}
332
- />
333
- </h4>
334
- </header>
335
- </div>
336
- <a
337
- aria-label={undefined}
338
- className="grommetux-anchor"
339
- href={undefined}
340
- onClick={[Function]}
341
- >
342
- test screen for 4
343
- <i
344
- className="icon icon-unknown"
345
- onClick={undefined}
346
- style={undefined}
347
- />
348
- </a>
349
- </nav>
350
- <footer
351
- className="grommetux-box grommetux-box--direction-row grommetux-box--align-center grommetux-box--pad-horizontal-medium grommetux-footer grommetux-footer--large"
352
- id={undefined}
353
- onClick={undefined}
354
- role={undefined}
355
- style={Object {}}
356
- tabIndex={undefined}
357
- >
358
- <a
359
- aria-hidden="true"
360
- className="grommetux-skip-link-anchor"
361
- id="skip-link-footer"
362
- tabIndex="-1"
363
- >
364
- Footer
365
- </a>
366
- <a
367
- aria-label={undefined}
368
- className="grommetux-anchor"
369
- href={undefined}
370
- onClick={[Function]}
371
- >
372
- <h3
373
- className="grommetux-heading grommetux-heading--strong grommetux-heading--margin-none"
374
- >
375
- Log Out
376
- </h3>
377
- </a>
378
- </footer>
379
- </div>
380
- `;