feature.spec.jsx 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. import React from 'react';
  2. import {mount} from 'enzyme';
  3. import Feature from 'app/components/acl/feature';
  4. import ConfigStore from 'app/stores/configStore';
  5. import HookStore from 'app/stores/hookStore';
  6. describe('Feature', function() {
  7. const organization = TestStubs.Organization({
  8. features: ['org-foo', 'org-bar', 'bar'],
  9. });
  10. const project = TestStubs.Project({
  11. features: ['project-foo', 'project-bar'],
  12. });
  13. const routerContext = TestStubs.routerContext([
  14. {
  15. organization,
  16. project,
  17. },
  18. ]);
  19. describe('as render prop', function() {
  20. const childrenMock = jest.fn().mockReturnValue(null);
  21. beforeEach(function() {
  22. childrenMock.mockClear();
  23. });
  24. it('has features', function() {
  25. const features = ['org-foo', 'project-foo'];
  26. mount(<Feature features={features}>{childrenMock}</Feature>, routerContext);
  27. expect(childrenMock).toHaveBeenCalledWith({
  28. hasFeature: true,
  29. features,
  30. organization,
  31. project,
  32. });
  33. });
  34. it('has features when requireAll is false', function() {
  35. const features = ['org-foo', 'project-foo', 'apple'];
  36. mount(
  37. <Feature features={features} requireAll={false}>
  38. {childrenMock}
  39. </Feature>,
  40. routerContext
  41. );
  42. expect(childrenMock).toHaveBeenCalledWith({
  43. hasFeature: true,
  44. organization,
  45. project,
  46. features,
  47. });
  48. });
  49. it('has no features', function() {
  50. mount(<Feature features={['org-baz']}>{childrenMock}</Feature>, routerContext);
  51. expect(childrenMock).toHaveBeenCalledWith({
  52. hasFeature: false,
  53. organization,
  54. project,
  55. features: ['org-baz'],
  56. });
  57. });
  58. it('calls render function when no features', function() {
  59. const noFeatureRenderer = jest.fn(() => null);
  60. mount(
  61. <Feature features={['org-baz']} renderDisabled={noFeatureRenderer}>
  62. {childrenMock}
  63. </Feature>,
  64. routerContext
  65. );
  66. expect(childrenMock).not.toHaveBeenCalled();
  67. expect(noFeatureRenderer).toHaveBeenCalledWith({
  68. hasFeature: false,
  69. children: childrenMock,
  70. organization,
  71. project,
  72. features: ['org-baz'],
  73. });
  74. });
  75. it('can specify org from props', function() {
  76. const customOrg = TestStubs.Organization({features: ['org-bazar']});
  77. mount(
  78. <Feature organization={customOrg} features={['org-bazar']}>
  79. {childrenMock}
  80. </Feature>,
  81. routerContext
  82. );
  83. expect(childrenMock).toHaveBeenCalledWith({
  84. hasFeature: true,
  85. organization: customOrg,
  86. project,
  87. features: ['org-bazar'],
  88. });
  89. });
  90. it('can specify project from props', function() {
  91. const customProject = TestStubs.Project({features: ['project-baz']});
  92. mount(
  93. <Feature project={customProject} features={['project-baz']}>
  94. {childrenMock}
  95. </Feature>,
  96. routerContext
  97. );
  98. expect(childrenMock).toHaveBeenCalledWith({
  99. hasFeature: true,
  100. organization,
  101. project: customProject,
  102. features: ['project-baz'],
  103. });
  104. });
  105. it('handles no org/project', function() {
  106. const features = ['org-foo', 'project-foo'];
  107. mount(
  108. <Feature organization={null} project={null} features={features}>
  109. {childrenMock}
  110. </Feature>,
  111. routerContext
  112. );
  113. expect(childrenMock).toHaveBeenCalledWith({
  114. hasFeature: false,
  115. organization: null,
  116. project: null,
  117. features,
  118. });
  119. });
  120. it('handles features prefixed with org/project', function() {
  121. mount(
  122. <Feature features={['organizations:org-bar']}>{childrenMock}</Feature>,
  123. routerContext
  124. );
  125. expect(childrenMock).toHaveBeenCalledWith({
  126. hasFeature: true,
  127. organization,
  128. project,
  129. features: ['organizations:org-bar'],
  130. });
  131. mount(<Feature features={['projects:bar']}>{childrenMock}</Feature>, routerContext);
  132. expect(childrenMock).toHaveBeenCalledWith({
  133. hasFeature: false,
  134. organization,
  135. project,
  136. features: ['projects:bar'],
  137. });
  138. });
  139. it('checks ConfigStore.config.features (e.g. `organizations:create`)', function() {
  140. ConfigStore.config = {
  141. features: new Set(['organizations:create']),
  142. };
  143. mount(
  144. <Feature features={['organizations:create']}>{childrenMock}</Feature>,
  145. routerContext
  146. );
  147. expect(childrenMock).toHaveBeenCalledWith({
  148. hasFeature: true,
  149. organization,
  150. project,
  151. features: ['organizations:create'],
  152. });
  153. });
  154. });
  155. describe('no children', function() {
  156. it('should display renderDisabled with no feature', function() {
  157. const wrapper = mount(
  158. <Feature features={['nope']} renderDisabled={() => <span>disabled</span>} />,
  159. routerContext
  160. );
  161. expect(wrapper.find('Feature span').text()).toBe('disabled');
  162. });
  163. it('should display be empty when on', function() {
  164. const wrapper = mount(
  165. <Feature features={['org-bar']} renderDisabled={() => <span>disabled</span>} />,
  166. routerContext
  167. );
  168. expect(wrapper.find('Feature').text()).toBe('');
  169. });
  170. });
  171. describe('as React node', function() {
  172. it('has features', function() {
  173. const wrapper = mount(
  174. <Feature features={['org-bar']}>
  175. <div>The Child</div>
  176. </Feature>,
  177. routerContext
  178. );
  179. expect(wrapper.find('Feature div').text()).toBe('The Child');
  180. });
  181. it('has no features', function() {
  182. const wrapper = mount(
  183. <Feature features={['org-baz']}>
  184. <div>The Child</div>
  185. </Feature>,
  186. routerContext
  187. );
  188. expect(wrapper.find('Feature div')).toHaveLength(0);
  189. });
  190. it('renders a default disabled component', function() {
  191. const wrapper = mount(
  192. <Feature features={['org-baz']} renderDisabled>
  193. <div>The Child</div>
  194. </Feature>,
  195. routerContext
  196. );
  197. expect(wrapper.exists('ComingSoon')).toBe(true);
  198. expect(wrapper.exists('Feature div[children="The Child"]')).not.toBe(true);
  199. });
  200. it('calls renderDisabled function when no features', function() {
  201. const noFeatureRenderer = jest.fn(() => null);
  202. const children = <div>The Child</div>;
  203. const wrapper = mount(
  204. <Feature features={['org-baz']} renderDisabled={noFeatureRenderer}>
  205. {children}
  206. </Feature>,
  207. routerContext
  208. );
  209. expect(wrapper.find('Feature div')).toHaveLength(0);
  210. expect(noFeatureRenderer).toHaveBeenCalledWith({
  211. hasFeature: false,
  212. children,
  213. organization,
  214. project,
  215. features: ['org-baz'],
  216. });
  217. });
  218. });
  219. describe('using HookStore for renderDisabled', function() {
  220. let hookFn;
  221. beforeEach(function() {
  222. hookFn = jest.fn(() => null);
  223. HookStore.hooks['feature-disabled:org-baz'] = [hookFn];
  224. HookStore.hooks['feature-disabled:test-hook'] = [hookFn];
  225. });
  226. afterEach(function() {
  227. delete HookStore.hooks['feature-disabled:org-baz'];
  228. });
  229. it('calls renderDisabled function from HookStore when no features', function() {
  230. const noFeatureRenderer = jest.fn(() => null);
  231. const children = <div>The Child</div>;
  232. const wrapper = mount(
  233. <Feature features={['org-baz']} renderDisabled={noFeatureRenderer}>
  234. {children}
  235. </Feature>,
  236. routerContext
  237. );
  238. expect(wrapper.find('Feature div')).toHaveLength(0);
  239. expect(noFeatureRenderer).not.toHaveBeenCalled();
  240. expect(hookFn).toHaveBeenCalledWith({
  241. hasFeature: false,
  242. children,
  243. organization,
  244. project,
  245. features: ['org-baz'],
  246. });
  247. });
  248. it('does not check hook store for multiple features', function() {
  249. const noFeatureRenderer = jest.fn(() => null);
  250. const wrapper = mount(
  251. <Feature features={['org-baz', 'org-bazar']} renderDisabled={noFeatureRenderer}>
  252. <div>The Child</div>
  253. </Feature>,
  254. routerContext
  255. );
  256. expect(wrapper.find('Feature div')).toHaveLength(0);
  257. expect(hookFn).not.toHaveBeenCalled();
  258. expect(noFeatureRenderer).toHaveBeenCalled();
  259. });
  260. it('uses hookName if provided', function() {
  261. const children = <div>The Child</div>;
  262. const wrapper = mount(
  263. <Feature features={['org-bazar']} hookName="test-hook">
  264. {children}
  265. </Feature>,
  266. routerContext
  267. );
  268. expect(wrapper.find('Feature div')).toHaveLength(0);
  269. expect(hookFn).toHaveBeenCalledWith({
  270. hasFeature: false,
  271. children,
  272. organization,
  273. project,
  274. features: ['org-bazar'],
  275. });
  276. });
  277. });
  278. });