feature.spec.jsx 8.1 KB

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