diff --git a/apps/public-docsite-resources/src/components/DemoPage.types.ts b/apps/public-docsite-resources/src/components/DemoPage.types.ts index 70f1a613de210..9679bbdf971a9 100644 --- a/apps/public-docsite-resources/src/components/DemoPage.types.ts +++ b/apps/public-docsite-resources/src/components/DemoPage.types.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-extraneous-dependencies export { IDocPageProps as IDemoPageProps } from '@fluentui/react-internal/lib/common/DocPage.types'; diff --git a/change/@fluentui-react-2020-10-21-16-15-31-tests-act.json b/change/@fluentui-react-2020-10-21-16-15-31-tests-act.json new file mode 100644 index 0000000000000..339e461d046fc --- /dev/null +++ b/change/@fluentui-react-2020-10-21-16-15-31-tests-act.json @@ -0,0 +1,8 @@ +{ + "type": "none", + "comment": "Remove incorrect usage of expect.toBeDefined", + "packageName": "@fluentui/react", + "email": "elcraig@microsoft.com", + "dependentChangeType": "none", + "date": "2020-10-21T23:14:57.115Z" +} diff --git a/change/@fluentui-react-experiments-2020-10-21-16-15-31-tests-act.json b/change/@fluentui-react-experiments-2020-10-21-16-15-31-tests-act.json new file mode 100644 index 0000000000000..e3ef47a39e3d0 --- /dev/null +++ b/change/@fluentui-react-experiments-2020-10-21-16-15-31-tests-act.json @@ -0,0 +1,8 @@ +{ + "type": "none", + "comment": "Remove incorrect usage of expect.toBeDefined", + "packageName": "@fluentui/react-experiments", + "email": "elcraig@microsoft.com", + "dependentChangeType": "none", + "date": "2020-10-21T23:14:12.634Z" +} diff --git a/change/@fluentui-react-internal-2020-10-21-16-15-31-tests-act.json b/change/@fluentui-react-internal-2020-10-21-16-15-31-tests-act.json new file mode 100644 index 0000000000000..db7b494d47ae8 --- /dev/null +++ b/change/@fluentui-react-internal-2020-10-21-16-15-31-tests-act.json @@ -0,0 +1,8 @@ +{ + "type": "none", + "comment": "Remove incorrect usage of expect.toBeDefined + add missing act() in tests", + "packageName": "@fluentui/react-internal", + "email": "elcraig@microsoft.com", + "dependentChangeType": "none", + "date": "2020-10-21T23:14:42.523Z" +} diff --git a/change/@fluentui-react-next-2020-10-21-16-15-31-tests-act.json b/change/@fluentui-react-next-2020-10-21-16-15-31-tests-act.json new file mode 100644 index 0000000000000..09286f1811279 --- /dev/null +++ b/change/@fluentui-react-next-2020-10-21-16-15-31-tests-act.json @@ -0,0 +1,8 @@ +{ + "type": "none", + "comment": "Properly log and propagate async exceptions from tests", + "packageName": "@fluentui/react-next", + "email": "elcraig@microsoft.com", + "dependentChangeType": "none", + "date": "2020-10-21T23:14:51.914Z" +} diff --git a/change/@uifabric-date-time-2020-10-21-16-15-31-tests-act.json b/change/@uifabric-date-time-2020-10-21-16-15-31-tests-act.json new file mode 100644 index 0000000000000..b47ca8fff4005 --- /dev/null +++ b/change/@uifabric-date-time-2020-10-21-16-15-31-tests-act.json @@ -0,0 +1,8 @@ +{ + "type": "none", + "comment": "Remove incorrect usage of expect.toBeDefined", + "packageName": "@fluentui/react-date-time", + "email": "elcraig@microsoft.com", + "dependentChangeType": "none", + "date": "2020-10-21T23:13:49.132Z" +} diff --git a/change/@uifabric-utilities-2020-10-21-16-15-31-tests-act.json b/change/@uifabric-utilities-2020-10-21-16-15-31-tests-act.json new file mode 100644 index 0000000000000..b57977d4b5966 --- /dev/null +++ b/change/@uifabric-utilities-2020-10-21-16-15-31-tests-act.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "Async: update setTimeout to use internal error logging method", + "packageName": "@fluentui/utilities", + "email": "elcraig@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-10-21T23:15:31.423Z" +} diff --git a/packages/a11y-testing/src/validators/validate.ts b/packages/a11y-testing/src/validators/validate.ts index 53be7bc9c079d..40bbceaa97025 100644 --- a/packages/a11y-testing/src/validators/validate.ts +++ b/packages/a11y-testing/src/validators/validate.ts @@ -5,7 +5,6 @@ import { SlotRule } from './../rules/rules'; export const validateSlot = (rule: SlotRule, baseTestFacade: TestFacade): void => { const slot = rule.getData(); const slotProps = slot.props || [{}]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any slotProps.forEach((props: Props) => { const testFacade = baseTestFacade.forProps(props); diff --git a/packages/fluentui/e2e/.eslintrc.json b/packages/fluentui/e2e/.eslintrc.json index b34ec5c034040..d18060309a88a 100644 --- a/packages/fluentui/e2e/.eslintrc.json +++ b/packages/fluentui/e2e/.eslintrc.json @@ -3,11 +3,7 @@ "root": true, "rules": { "import/no-default-export": "off", - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": true - } - ] + "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], + "no-console": "off" } } diff --git a/packages/react-date-time/src/components/DatePicker/DatePicker.test.tsx b/packages/react-date-time/src/components/DatePicker/DatePicker.test.tsx index 93a78f66f8a7b..c559e43076406 100644 --- a/packages/react-date-time/src/components/DatePicker/DatePicker.test.tsx +++ b/packages/react-date-time/src/components/DatePicker/DatePicker.test.tsx @@ -97,7 +97,7 @@ describe('DatePicker', () => { const datePicker = mount(); const textField = datePicker.find('input'); - expect(textField).toBeDefined(); + expect(textField).toHaveLength(1); textField.simulate('change', { target: { value: 'Jan 1 2030' } }).simulate('blur'); textField.simulate('change', { target: { value: '' } }).simulate('blur'); diff --git a/packages/react-experiments/src/components/FloatingSuggestions/Suggestions/SuggestionsControl.tests.tsx b/packages/react-experiments/src/components/FloatingSuggestions/Suggestions/SuggestionsControl.test.tsx similarity index 100% rename from packages/react-experiments/src/components/FloatingSuggestions/Suggestions/SuggestionsControl.tests.tsx rename to packages/react-experiments/src/components/FloatingSuggestions/Suggestions/SuggestionsControl.test.tsx diff --git a/packages/react-experiments/src/components/SelectedItemsList/SelectedItemsList.test.tsx b/packages/react-experiments/src/components/SelectedItemsList/SelectedItemsList.test.tsx index cf6d8a10f1dd4..f13545b793302 100644 --- a/packages/react-experiments/src/components/SelectedItemsList/SelectedItemsList.test.tsx +++ b/packages/react-experiments/src/components/SelectedItemsList/SelectedItemsList.test.tsx @@ -41,7 +41,7 @@ describe('SelectedItemsList', () => { ]} />, ); - expect(wrapper).toBeDefined(); + expect(wrapper.exists()).toBeTruthy(); expect(wrapper.find('div').length).toEqual(3); expect( wrapper @@ -75,7 +75,7 @@ describe('SelectedItemsList', () => { onItemsRemoved={removeItems} />, ); - expect(wrapper).toBeDefined(); + expect(wrapper.exists()).toBeTruthy(); expect(wrapper.find('div').length).toEqual(3); expect( wrapper @@ -102,7 +102,7 @@ describe('SelectedItemsList', () => { ]} />, ); - expect(wrapper).toBeDefined(); + expect(wrapper.exists()).toBeTruthy(); expect(wrapper.find('div').length).toEqual(3); expect( wrapper diff --git a/packages/react-internal/__mocks__/@fluentui/utilities.ts b/packages/react-internal/__mocks__/@fluentui/utilities.ts index e88d4290a834c..e0c338e3413dc 100644 --- a/packages/react-internal/__mocks__/@fluentui/utilities.ts +++ b/packages/react-internal/__mocks__/@fluentui/utilities.ts @@ -41,6 +41,12 @@ class MockAsync extends Async { super.dispose(); } + + protected _logError(e: any) { + super._logError(e); + // Don't eat errors thrown from async callbacks + throw e; + } } export { MockAsync as Async }; diff --git a/packages/react-internal/src/compat/components/Button/Button.test.tsx b/packages/react-internal/src/compat/components/Button/Button.test.tsx index e1cd21a6fed88..3b7b78669eb57 100644 --- a/packages/react-internal/src/compat/components/Button/Button.test.tsx +++ b/packages/react-internal/src/compat/components/Button/Button.test.tsx @@ -208,10 +208,8 @@ describe('Button', () => { expect(button.getAttribute('aria-label')).toBeNull(); expect(button.getAttribute('aria-labelledby')).toEqual(button.querySelector(`.ms-Button-label`)!.id); - expect(button.getAttribute('aria-labelledby')).toBeDefined(); expect(button.getAttribute('aria-describedby')).toEqual(button.querySelector('.some-screenreader-class')!.id); - expect(button.getAttribute('aria-describedby')).toBeDefined(); }); it('applies aria-describedby to an IconButton', () => { @@ -226,10 +224,8 @@ describe('Button', () => { expect(button.getAttribute('aria-label')).toBeNull(); expect(button.getAttribute('aria-labelledby')).toBeNull(); - expect(button.getAttribute('aria-labelledby')).toBeDefined(); expect(button.getAttribute('aria-describedby')).toEqual(button.querySelector('.some-screenreader-class')!.id); - expect(button.getAttribute('aria-describedby')).toBeDefined(); }); it('applies aria-labelledby and aria-describedby to a CompoundButton with ariaDescription', () => { @@ -246,10 +242,8 @@ describe('Button', () => { expect(button.getAttribute('aria-label')).toBeNull(); expect(button.getAttribute('aria-labelledby')).toEqual(button.querySelector('.ms-Button-label')!.id); - expect(button.getAttribute('aria-labelledby')).toBeDefined(); expect(button.getAttribute('aria-describedby')).toEqual(button.querySelector('.some-screenreader-class')!.id); - expect(button.getAttribute('aria-describedby')).toBeDefined(); }); it( @@ -263,10 +257,8 @@ describe('Button', () => { expect(button.getAttribute('aria-label')).toBeNull(); expect(button.getAttribute('aria-labelledby')).toEqual(button.querySelector('.ms-Button-label')!.id); - expect(button.getAttribute('aria-labelledby')).toBeDefined(); expect(button.getAttribute('aria-describedby')).toEqual(button.querySelector('.ms-Button-description')!.id); - expect(button.getAttribute('aria-describedby')).toBeDefined(); }, ); @@ -983,15 +975,14 @@ describe('Button', () => { const button = render(element); - expect(button).toBeDefined(); ReactTestUtils.Simulate.click(button); // get the menu id from the button's aria attribute const menuId = button.getAttribute('aria-owns'); - expect(menuId).toBeDefined(); + expect(menuId).toBeTruthy(); const menuDOM = button.ownerDocument!.getElementById(menuId as string); - expect(menuDOM).toBeDefined(); + expect(menuDOM).toBeTruthy(); return menuDOM as HTMLElement; } @@ -1001,7 +992,7 @@ describe('Button', () => { expect(contextualMenuElement).not.toBeNull(); expect(contextualMenuElement.getAttribute('aria-label')).toBeNull(); - expect(contextualMenuElement.getAttribute('aria-labelledBy')).toBeDefined(); + expect(contextualMenuElement.getAttribute('aria-labelledBy')).toBeTruthy(); }); it('If button has a text child, contextual menu has aria-labelledBy attribute set', () => { @@ -1048,12 +1039,11 @@ describe('Button', () => { const button = render(element); - expect(button).toBeDefined(); ReactTestUtils.Simulate.click(button); // get the menu id from the button's aria attribute const menuId = button.getAttribute('aria-owns'); - expect(menuId).toBeDefined(); + expect(menuId).toBeTruthy(); const contextualMenuElement = button.ownerDocument!.getElementById(menuId as string); expect(contextualMenuElement).not.toBeNull(); diff --git a/packages/react-internal/src/components/ChoiceGroup/ChoiceGroup.test.tsx b/packages/react-internal/src/components/ChoiceGroup/ChoiceGroup.test.tsx index fb7cf60cd9f9b..a3f96781adf79 100644 --- a/packages/react-internal/src/components/ChoiceGroup/ChoiceGroup.test.tsx +++ b/packages/react-internal/src/components/ChoiceGroup/ChoiceGroup.test.tsx @@ -53,7 +53,7 @@ describe('ChoiceGroup', () => { it('does not use className prop from parent on label', () => { choiceGroup = mount(); const label = choiceGroup.getDOMNode().querySelector('label'); - expect(label).toBeDefined(); + expect(label).toBeTruthy(); expect(label!.textContent).toBe('test label'); expect(label!.className).not.toContain('testClassName'); }); diff --git a/packages/react-internal/src/components/ColorPicker/ColorRectangle/ColorRectangle.test.tsx b/packages/react-internal/src/components/ColorPicker/ColorRectangle/ColorRectangle.test.tsx index 79b3fa63fbe89..ae8e33577e1d2 100644 --- a/packages/react-internal/src/components/ColorPicker/ColorRectangle/ColorRectangle.test.tsx +++ b/packages/react-internal/src/components/ColorPicker/ColorRectangle/ColorRectangle.test.tsx @@ -71,7 +71,7 @@ describe('ColorRectangle', () => { const descriptionId = element.getAttribute('aria-describedby'); expect(descriptionId).toBeTruthy(); const descriptionEl = element.querySelectorAll('#' + descriptionId)[0]; - expect(descriptionEl).toBeDefined(); + expect(descriptionEl).toBeTruthy(); expect(descriptionEl.textContent).toBe( 'Use left and right arrow keys to set saturation. Use up and down arrow keys to set brightness.', ); diff --git a/packages/react-internal/src/components/CommandBar/CommandBar.deprecated.test.tsx b/packages/react-internal/src/components/CommandBar/CommandBar.deprecated.test.tsx index f5dbc01e12535..5fbccca86b73a 100644 --- a/packages/react-internal/src/components/CommandBar/CommandBar.deprecated.test.tsx +++ b/packages/react-internal/src/components/CommandBar/CommandBar.deprecated.test.tsx @@ -58,7 +58,7 @@ describe('CommandBar', () => { menuItem.simulate('click'); - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); }); it('keeps menu open after update if item is still present', () => { @@ -87,7 +87,7 @@ describe('CommandBar', () => { menuItem.simulate('click'); // Make sure the menu is open before the re-render - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); // Update the props, and re-render commandBar.setProps({ @@ -100,7 +100,7 @@ describe('CommandBar', () => { }); // Make sure the menu is still open after the re-render - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); }); it('closes menu after update if item is not longer present', () => { @@ -129,7 +129,7 @@ describe('CommandBar', () => { menuItem.simulate('click'); // Make sure the menu is open before the re-render - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); // Update the props, and re-render commandBar.setProps({ @@ -164,7 +164,7 @@ describe('CommandBar', () => { menuItem.simulate('click'); // Make sure the menu is open before the re-render - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); // Update the props items[0].subMenuProps.items[0].className = 'SubMenuClassUpdate'; @@ -175,6 +175,6 @@ describe('CommandBar', () => { }); // Make sure the menu is still open after the re-render - expect(document.querySelector('.SubMenuClassUpdate')).toBeDefined(); + expect(document.querySelector('.SubMenuClassUpdate')).toBeTruthy(); }); }); diff --git a/packages/react-internal/src/components/CommandBar/CommandBar.test.tsx b/packages/react-internal/src/components/CommandBar/CommandBar.test.tsx index 5829f8892ed77..60206beeb7cb2 100644 --- a/packages/react-internal/src/components/CommandBar/CommandBar.test.tsx +++ b/packages/react-internal/src/components/CommandBar/CommandBar.test.tsx @@ -84,7 +84,7 @@ describe('CommandBar', () => { menuItem.simulate('click'); - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); }); it('passes event and item to button onClick callbacks', () => { @@ -137,7 +137,7 @@ describe('CommandBar', () => { menuItem.simulate('click'); // Make sure the menu is open before the re-render - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); // Update the props, and re-render commandBar.setProps({ @@ -150,7 +150,7 @@ describe('CommandBar', () => { }); // Make sure the menu is still open after the re-render - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); }); it('closes menu after update if item is not longer present', () => { @@ -179,7 +179,7 @@ describe('CommandBar', () => { menuItem.simulate('click'); // Make sure the menu is open before the re-render - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); // Update the props, and re-render commandBar.setProps({ @@ -254,7 +254,7 @@ describe('CommandBar', () => { menuItem.simulate('click'); // Make sure the menu is open before the re-render - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); // Update the props items[0].subMenuProps.items[0].className = 'SubMenuClassUpdate'; @@ -265,6 +265,6 @@ describe('CommandBar', () => { }); // Make sure the menu is still open after the re-render - expect(document.querySelector('.SubMenuClassUpdate')).toBeDefined(); + expect(document.querySelector('.SubMenuClassUpdate')).toBeTruthy(); }); }); diff --git a/packages/react-internal/src/components/ContextualMenu/ContextualMenu.deprecated.test.tsx b/packages/react-internal/src/components/ContextualMenu/ContextualMenu.deprecated.test.tsx index a79818b1ced1e..917028627d10a 100644 --- a/packages/react-internal/src/components/ContextualMenu/ContextualMenu.deprecated.test.tsx +++ b/packages/react-internal/src/components/ContextualMenu/ContextualMenu.deprecated.test.tsx @@ -50,7 +50,10 @@ describe('ContextualMenu', () => { }); it('includes the classNames on ContextualMenuItem(s)', () => { - const items: IContextualMenuItem[] = [{ name: 'Test 1', key: 'Test1' }]; + const items: IContextualMenuItem[] = [ + { text: 'Header', key: 'Header', itemType: ContextualMenuItemType.Header }, + { name: 'Test 1', key: 'Test1' }, + ]; const getClassNames = () => { return { @@ -64,7 +67,7 @@ describe('ContextualMenu', () => { }; ReactTestUtils.renderIntoDocument( - , + , ); const container = document.querySelector('.containerFoo') as HTMLElement; @@ -73,11 +76,11 @@ describe('ContextualMenu', () => { const header = document.querySelector('.headerFoo') as HTMLElement; const title = document.querySelector('.titleFoo') as HTMLElement; - expect(container).toBeDefined(); - expect(rootEl).toBeDefined(); - expect(list).toBeDefined(); - expect(header).toBeDefined(); - expect(title).toBeDefined(); + expect(container).toBeTruthy(); + expect(rootEl).toBeTruthy(); + expect(list).toBeTruthy(); + expect(header).toBeTruthy(); + expect(title).toBeTruthy(); }); it('applies in-line style property if present on ContextualMenuItem', () => { @@ -260,7 +263,7 @@ describe('ContextualMenu', () => { ReactTestUtils.Simulate.keyDown(menuItem, { which: KeyCodes.right }); }); - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); }); }); @@ -291,7 +294,7 @@ describe('getItemClassNames', () => { iconClassName, ); - expect(itemClassNames).toBeDefined(); + expect(itemClassNames).toBeTruthy(); }); it('applies custom classNames to style slots', () => { diff --git a/packages/react-internal/src/components/ContextualMenu/ContextualMenu.test.tsx b/packages/react-internal/src/components/ContextualMenu/ContextualMenu.test.tsx index 18b873f7d991f..217017172164a 100644 --- a/packages/react-internal/src/components/ContextualMenu/ContextualMenu.test.tsx +++ b/packages/react-internal/src/components/ContextualMenu/ContextualMenu.test.tsx @@ -314,7 +314,7 @@ describe('ContextualMenu', () => { ReactTestUtils.Simulate.keyDown(menuItem, { which: KeyCodes.right }); }); - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); }); it('opens a submenu item on click', () => { @@ -344,7 +344,7 @@ describe('ContextualMenu', () => { ReactTestUtils.Simulate.click(menuItem); }); - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); }); it('opens a submenu item on alt+Down', () => { @@ -372,7 +372,7 @@ describe('ContextualMenu', () => { ReactTestUtils.Simulate.keyDown(menuItem, { which: KeyCodes.down, altKey: true }); }); - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); }); it('closes a submenu item on alt+up', () => { @@ -525,7 +525,7 @@ describe('ContextualMenu', () => { menuList = document.querySelectorAll('ul.ms-ContextualMenu-list'); expect(menuList.length).toEqual(2); - expect(document.querySelector('.SubMenuClass')).toBeDefined(); + expect(document.querySelector('.SubMenuClass')).toBeTruthy(); }); it('opens a splitbutton submenu item on touch start', () => { @@ -909,7 +909,7 @@ describe('ContextualMenu', () => { }); const callout = document.querySelector('.ms-Callout') as HTMLElement; - expect(callout).toBeDefined(); + expect(callout).toBeTruthy(); expect(callout.classList.contains('ms-ContextualMenu-Callout')).toBeTruthy(); expect(callout.classList.contains('foo')).toBeTruthy(); }); @@ -1440,6 +1440,7 @@ describe('ContextualMenu', () => { const items: IContextualMenuItem[] = [ { text: 'TestText 1', key: 'TestKey1', canCheck: true, isChecked: true }, { text: 'TestText 2', key: 'TestKey2', canCheck: true, isChecked: true }, + { text: 'Header', key: 'Header', itemType: ContextualMenuItemType.Header }, { text: 'TestText 3', key: 'TestKey3', canCheck: true, isChecked: true }, { text: 'TestText 4', key: 'TestKey4', canCheck: true, isChecked: true }, ]; @@ -1457,7 +1458,7 @@ describe('ContextualMenu', () => { ReactTestUtils.act(() => { ReactTestUtils.renderIntoDocument( - , + , ); }); @@ -1467,11 +1468,11 @@ describe('ContextualMenu', () => { const header = document.querySelector('.ms-ContextualMenu-header') as HTMLElement; const title = document.querySelector('.ms-ContextualMenu-title') as HTMLElement; - expect(container).toBeDefined(); - expect(rootEl).toBeDefined(); - expect(list).toBeDefined(); - expect(header).toBeDefined(); - expect(title).toBeDefined(); + expect(container).toBeTruthy(); + expect(rootEl).toBeTruthy(); + expect(list).toBeTruthy(); + expect(header).toBeTruthy(); + expect(title).toBeTruthy(); }); it('applies styles per ContextualMenuItem if provided', () => { diff --git a/packages/react-internal/src/components/FloatingPicker/Suggestions/SuggestionsControl.tests.tsx b/packages/react-internal/src/components/FloatingPicker/Suggestions/SuggestionsControl.test.tsx similarity index 100% rename from packages/react-internal/src/components/FloatingPicker/Suggestions/SuggestionsControl.tests.tsx rename to packages/react-internal/src/components/FloatingPicker/Suggestions/SuggestionsControl.test.tsx diff --git a/packages/react-internal/src/components/HoverCard/HoverCard.test.tsx b/packages/react-internal/src/components/HoverCard/HoverCard.test.tsx index e3b9a82840f0a..2839440588109 100644 --- a/packages/react-internal/src/components/HoverCard/HoverCard.test.tsx +++ b/packages/react-internal/src/components/HoverCard/HoverCard.test.tsx @@ -149,7 +149,7 @@ describe('HoverCard', () => { ); jest.useFakeTimers(); - expect(hoverCard).toBeDefined(); + expect(hoverCard).toBeTruthy(); // firing the onCardVisible callback after the component is updated. component.setState({ isHoverCardVisible: true }); diff --git a/packages/react-internal/src/components/Image/Image.test.tsx b/packages/react-internal/src/components/Image/Image.test.tsx index 86a4e0ec66ae1..94160851ec65e 100644 --- a/packages/react-internal/src/components/Image/Image.test.tsx +++ b/packages/react-internal/src/components/Image/Image.test.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { create } from '@fluentui/utilities/lib/test'; import { Image } from './Image'; import { ImageBase } from './Image.base'; -import { ImageFit } from './Image.types'; +import { ImageFit, ImageCoverStyle } from './Image.types'; import { act } from 'react-dom/test-utils'; import { isConformant } from '../../common/isConformant'; @@ -68,7 +68,13 @@ describe('Image', () => { it('can cover a landscape (wide) parent element with a square image', () => { const component = mount(
- +
, ); @@ -79,13 +85,19 @@ describe('Image', () => { component.find('img').simulate('load'); }); - expect(component.getDOMNode().querySelector('.ms-Image-image--portrait')).toBeDefined(); + expect(component.getDOMNode().querySelector('.ms-Image-image--portrait')).toBeTruthy(); }); it('can cover a portrait (tall) parent element with a square image', () => { const component = mount(
- +
, ); @@ -96,7 +108,7 @@ describe('Image', () => { act(() => { component.find('img').simulate('load'); }); - expect(component.getDOMNode().querySelector('.ms-Image-image--landscape')).toBeDefined(); + expect(component.getDOMNode().querySelector('.ms-Image-image--landscape')).toBeTruthy(); }); it('renders ImageFit.centerContain correctly', () => { diff --git a/packages/react-internal/src/components/Layer/Layer.test.tsx b/packages/react-internal/src/components/Layer/Layer.test.tsx index a9bcbc4fd6527..eb3ecb7772ac1 100644 --- a/packages/react-internal/src/components/Layer/Layer.test.tsx +++ b/packages/react-internal/src/components/Layer/Layer.test.tsx @@ -74,8 +74,8 @@ describe('Layer', () => { const parentElement = appElement.querySelector('#parent'); - expect(parentElement).toBeDefined(); - expect(parentElement!.ownerDocument).toBeDefined(); + expect(parentElement).toBeTruthy(); + expect(parentElement!.ownerDocument).toBeTruthy(); const childElement = appElement.querySelector('#child') as Element; diff --git a/packages/react-internal/src/components/OverflowSet/OverflowSet.test.tsx b/packages/react-internal/src/components/OverflowSet/OverflowSet.test.tsx index c3adca70ec0dd..6af2be8a00ba3 100644 --- a/packages/react-internal/src/components/OverflowSet/OverflowSet.test.tsx +++ b/packages/react-internal/src/components/OverflowSet/OverflowSet.test.tsx @@ -2,6 +2,7 @@ import { shallow } from 'enzyme'; import { ReactWrapper, mount } from 'enzyme'; import * as React from 'react'; import { create } from '@fluentui/utilities/lib/test'; +import * as ReactTestUtils from 'react-dom/test-utils'; import * as sinon from 'sinon'; import { CommandBarButton } from '../../compat/Button'; import { IKeytipProps } from '../../Keytip'; @@ -33,6 +34,11 @@ function getPersistedKeytip(keytipManager: KeytipManager, keySequences: string[] return ktp ? ktp.keytip : undefined; } +const runAllTimers = () => + ReactTestUtils.act(() => { + jest.runAllTimers(); + }); + describe('OverflowSet', () => { describe('snapshot tests', () => { test('basicSnapshot', () => { @@ -287,7 +293,7 @@ describe('OverflowSet', () => { keytipTree.currentKeytip = keytipTree.root; // Open the overflow menu layerRef.current!.processInput('x'); - jest.runAllTimers(); + runAllTimers(); // Opening the submenu should register the two keytips for those items const keytip3 = getKeytip(keytipManager, ['c']); @@ -328,7 +334,7 @@ describe('OverflowSet', () => { keytipTree.currentKeytip = keytipTree.root; // Open the overflow menu layerRef.current!.processInput('x'); - jest.runAllTimers(); + runAllTimers(); // item3 const item3Keytip = getKeytip(keytipManager, overflowKeytips.overflowItemKeytip3.keySequences); @@ -509,7 +515,7 @@ describe('OverflowSet', () => { const subMenu6Keytip = getKeytip(keytipManager, modifiedKeytip6Sequence); expect(subMenu6Keytip).toBeDefined(); expect(subMenu6Keytip!.overflowSetSequence![0]).toEqual('x'); - jest.runAllTimers(); + runAllTimers(); // Those two keytips should now be visible in the Layer and have overflowSetSequence set const submenuKeytips = layerRef.current!.state.visibleKeytips; @@ -605,7 +611,7 @@ describe('OverflowSet', () => { keytipTree.currentKeytip = keytipTree.root; layerRef.current!.processInput('d'); // Wait for the menu to open - jest.runAllTimers(); + runAllTimers(); // Those two keytips should now be visible in the Layer and have overflowSetSequence set const submenuKeytips = layerRef.current!.state.visibleKeytips; @@ -688,7 +694,6 @@ describe('OverflowSet', () => { keytipTree.currentKeytip = keytipTree.root; layerRef.current!.processInput('d'); - jest.runAllTimers(); // Those two keytips should now be visible in the Layer and have overflowSetSequence set const submenuKeytips = layerRef.current!.state.visibleKeytips; diff --git a/packages/react-internal/src/components/TeachingBubble/TeachingBubble.test.tsx b/packages/react-internal/src/components/TeachingBubble/TeachingBubble.test.tsx index de1dab77740ba..8f611094899d4 100644 --- a/packages/react-internal/src/components/TeachingBubble/TeachingBubble.test.tsx +++ b/packages/react-internal/src/components/TeachingBubble/TeachingBubble.test.tsx @@ -138,7 +138,7 @@ describe('TeachingBubble', () => { ReactTestUtils.renderIntoDocument(); setTimeout(() => { const callout = document.querySelector('.ms-Callout') as HTMLElement; - expect(callout).toBeDefined(); + expect(callout).toBeTruthy(); expect(callout.classList.contains('ms-TeachingBubble')).toBeTruthy(); expect(callout.classList.contains('foo')).toBeTruthy(); }, 0); diff --git a/packages/react-internal/src/components/TextField/TextField.test.tsx b/packages/react-internal/src/components/TextField/TextField.test.tsx index a7207b5925b83..9763d82935bfb 100644 --- a/packages/react-internal/src/components/TextField/TextField.test.tsx +++ b/packages/react-internal/src/components/TextField/TextField.test.tsx @@ -202,7 +202,7 @@ describe('TextField basic props', () => { const labelDOM = wrapper.getDOMNode().querySelector('label'); // Assert the input ID and label FOR attribute are the same. - expect(inputDOM!.id).toBeDefined(); + expect(inputDOM!.id).toBeTruthy(); expect(inputDOM!.id).toEqual(labelDOM!.htmlFor); }); @@ -769,7 +769,7 @@ describe('TextField', () => { const onSelect = () => { const selectedText = window.getSelection(); - expect(selectedText).toBeDefined(); + expect(selectedText).toBeTruthy(); expect(selectedText!.toString()).toEqual(initialValue); }; diff --git a/packages/react-internal/src/components/pickers/BasePicker.test.tsx b/packages/react-internal/src/components/pickers/BasePicker.test.tsx index 863d8380194de..ceb81aca15562 100644 --- a/packages/react-internal/src/components/pickers/BasePicker.test.tsx +++ b/packages/react-internal/src/components/pickers/BasePicker.test.tsx @@ -68,6 +68,11 @@ describe('BasePicker', () => {
{basicRenderer(props)}
); + const runAllTimers = () => + ReactTestUtils.act(() => { + jest.runAllTimers(); + }); + it('renders correctly', () => { const component = renderer.create( { input.focus(); input.value = 'bl'; ReactTestUtils.Simulate.input(input); - jest.runAllTimers(); + runAllTimers(); - expect(getSuggestions(document)).toBeDefined(); + expect(getSuggestions(document)).toBeTruthy(); const suggestionOptions = document.querySelectorAll('.ms-Suggestions-itemButton'); expect(suggestionOptions.length).toEqual(2); @@ -169,9 +174,9 @@ describe('BasePicker', () => { input.focus(); input.value = 'asdff'; ReactTestUtils.Simulate.input(input); - jest.runAllTimers(); + runAllTimers(); - expect(getSuggestions(document)).toBeDefined(); + expect(getSuggestions(document)).toBeTruthy(); const suggestionOptions = document.querySelectorAll('.ms-Suggestions-itemButton'); expect(suggestionOptions.length).toEqual(0); @@ -220,9 +225,9 @@ describe('BasePicker', () => { input.focus(); input.value = 'asdff'; ReactTestUtils.Simulate.input(input); - jest.runAllTimers(); + runAllTimers(); - expect(getSuggestions(document)).toBeDefined(); + expect(getSuggestions(document)).toBeTruthy(); const forceButton = document.querySelectorAll('[data-automationid=sug-forceResolve]'); expect(forceButton.length).toEqual(1); @@ -254,7 +259,7 @@ describe('BasePicker', () => { input.focus(); input.value = 'bl'; ReactTestUtils.Simulate.input(input); - jest.runAllTimers(); + runAllTimers(); const suggestionOptions = document.querySelectorAll('.ms-Suggestions-itemButton'); ReactTestUtils.Simulate.click(suggestionOptions[0]); @@ -330,7 +335,7 @@ describe('BasePicker', () => { const input = document.querySelector('.ms-BasePicker-input') as HTMLInputElement; input.focus(); - expect(getSuggestions(document)).toBeDefined(); + expect(getSuggestions(document)).toBeTruthy(); const suggestionOptions = document.querySelectorAll('.ms-Suggestions-itemButton'); expect(suggestionOptions.length).toEqual(15); @@ -359,7 +364,7 @@ describe('BasePicker', () => { input.focus(); input.value = 'asdff'; ReactTestUtils.Simulate.input(input); - jest.runAllTimers(); + runAllTimers(); expect(getSuggestions(document)).toBeTruthy(); @@ -388,7 +393,7 @@ describe('BasePicker', () => { input.focus(); input.value = 'asdff'; ReactTestUtils.Simulate.input(input); - jest.runAllTimers(); + runAllTimers(); expect(getSuggestions(document)).toBeTruthy(); @@ -397,12 +402,13 @@ describe('BasePicker', () => { expect(getSuggestions(document)).toBeFalsy(); ReactTestUtils.Simulate.click(input, { button: 0 }); - jest.runAllTimers(); + runAllTimers(); expect(getSuggestions(document)).toBeTruthy(); }); it('Opens menu when input refocused after search has happened', () => { + expect(getSuggestions(document)).toBeFalsy(); jest.useFakeTimers(); document.body.appendChild(root); @@ -425,7 +431,10 @@ describe('BasePicker', () => { input.focus(); input.value = 'bl'; ReactTestUtils.Simulate.input(input); - jest.runAllTimers(); + // For some reason with act() this has to be run twice to make the callout dismiss callback + // actually be called? + runAllTimers(); + runAllTimers(); expect(getSuggestions(document)).toBeTruthy(); @@ -434,9 +443,9 @@ describe('BasePicker', () => { // Implicit test to ensure suggestions are dismissed when focus lost expect(getSuggestions(document)).toBeFalsy(); - jest.runAllTimers(); + runAllTimers(); input.focus(); - jest.runAllTimers(); + runAllTimers(); expect(getSuggestions(document)).toBeTruthy(); }); @@ -465,7 +474,7 @@ describe('BasePicker', () => { const input = document.querySelector('.ms-BasePicker-input') as HTMLInputElement; input.focus(); - jest.runAllTimers(); + runAllTimers(); expect(count).toEqual(1); @@ -507,9 +516,9 @@ describe('BasePicker', () => { input.focus(); input.value = 'b'; ReactTestUtils.Simulate.input(input); - jest.runAllTimers(); + runAllTimers(); - expect(getSuggestions(document)).toBeDefined(); + expect(getSuggestions(document)).toBeTruthy(); const moreButton = document.querySelector('[data-automationid=sug-searchForMore]') as HTMLElement; expect(moreButton).toBeTruthy(); diff --git a/packages/react-internal/src/components/pickers/PeoplePicker/PeoplePicker.test.tsx b/packages/react-internal/src/components/pickers/PeoplePicker/PeoplePicker.test.tsx index 9d5d1cb006d47..ace1c125c5524 100644 --- a/packages/react-internal/src/components/pickers/PeoplePicker/PeoplePicker.test.tsx +++ b/packages/react-internal/src/components/pickers/PeoplePicker/PeoplePicker.test.tsx @@ -47,10 +47,12 @@ describe('PeoplePicker', () => { input.value = 'Valentyna'; ReactTestUtils.Simulate.input(input); - jest.runAllTimers(); + ReactTestUtils.act(() => { + jest.runAllTimers(); + }); const suggestions = document.querySelector('.ms-Suggestions') as HTMLInputElement; - expect(suggestions).toBeDefined(); + expect(suggestions).toBeTruthy(); const suggestionOptions = document.querySelectorAll('.ms-Suggestions-itemButton'); expect(suggestionOptions.length).toEqual(1); @@ -61,7 +63,7 @@ describe('PeoplePicker', () => { expect(currentPicker![0].text).toEqual('Valentyna Lovrique'); const removeButton = document.querySelector('.ms-PickerItem-removeButton') as HTMLButtonElement; - expect(removeButton).toBeDefined(); + expect(removeButton).toBeTruthy(); ReactTestUtils.Simulate.click(removeButton); const currentPickerAfterRemove = picker.current!.items; @@ -70,7 +72,7 @@ describe('PeoplePicker', () => { ReactDOM.unmountComponentAtNode(root); }); - it('can not remove people when disabled', () => { + it('cannot remove people when disabled', () => { const root = document.createElement('div'); document.body.appendChild(root); @@ -90,7 +92,7 @@ describe('PeoplePicker', () => { expect(currentPicker).toHaveLength(1); const removeButton = document.querySelector('.ms-PickerItem-removeButton') as HTMLButtonElement; - expect(removeButton).toBeDefined(); + expect(removeButton).toBeTruthy(); ReactTestUtils.Simulate.click(removeButton); const currentPickerAfterClick = picker.current!.items; diff --git a/packages/react-internal/src/components/pickers/TagPicker/TagPicker.test.tsx b/packages/react-internal/src/components/pickers/TagPicker/TagPicker.test.tsx index 3da3f24aec14a..6a1187abb80d8 100644 --- a/packages/react-internal/src/components/pickers/TagPicker/TagPicker.test.tsx +++ b/packages/react-internal/src/components/pickers/TagPicker/TagPicker.test.tsx @@ -31,6 +31,11 @@ function onResolveSuggestions(text: string): ITag[] { .map(item => ({ key: item, name: item })); } +const runAllTimers = () => + ReactTestUtils.act(() => { + jest.runAllTimers(); + }); + describe('TagPicker', () => { beforeEach(() => { resetIds(); @@ -74,11 +79,11 @@ describe('TagPicker', () => { input.value = 'bl'; ReactTestUtils.Simulate.input(input); - jest.runAllTimers(); + runAllTimers(); const suggestions = document.querySelector('.ms-Suggestions') as HTMLInputElement; - expect(suggestions).toBeDefined(); + expect(suggestions).toBeTruthy(); const suggestionOptions = document.querySelectorAll('.ms-Suggestions-itemButton'); expect(suggestionOptions.length).toEqual(2); @@ -108,7 +113,7 @@ describe('TagPicker', () => { input.focus(); input.value = 'bl'; ReactTestUtils.Simulate.input(input); - jest.runAllTimers(); + runAllTimers(); const suggestionOptions = document.querySelectorAll('.ms-Suggestions-itemButton'); @@ -152,7 +157,7 @@ describe('TagPicker', () => { input.focus(); input.value = 'bl'; ReactTestUtils.Simulate.input(input); - jest.runAllTimers(); + runAllTimers(); const suggestionOptions = document.querySelectorAll('.ms-Suggestions-itemButton'); diff --git a/packages/react-internal/src/utilities/decorators/withResponsiveMode.test.tsx b/packages/react-internal/src/utilities/decorators/withResponsiveMode.test.tsx index 5a2e69d078d6c..1117de25e57fd 100644 --- a/packages/react-internal/src/utilities/decorators/withResponsiveMode.test.tsx +++ b/packages/react-internal/src/utilities/decorators/withResponsiveMode.test.tsx @@ -15,7 +15,7 @@ describe('withResponsiveMode', () => { setSSR(true); setResponsiveMode(ResponsiveMode.large); - expect(() => ReactTestUtils.renderIntoDocument()).toBeDefined(); + expect(() => ReactTestUtils.renderIntoDocument()).toBeTruthy(); setSSR(false); }); diff --git a/packages/react-next/__mocks__/@fluentui/utilities.ts b/packages/react-next/__mocks__/@fluentui/utilities.ts index e88d4290a834c..07031ae7eaa0d 100644 --- a/packages/react-next/__mocks__/@fluentui/utilities.ts +++ b/packages/react-next/__mocks__/@fluentui/utilities.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ export * from '@fluentui/utilities'; import { Async } from '@fluentui/utilities'; @@ -41,6 +42,12 @@ class MockAsync extends Async { super.dispose(); } + + protected _logError(e: any) { + super._logError(e); + // Don't eat errors thrown from async callbacks + throw e; + } } export { MockAsync as Async }; diff --git a/packages/react/__mocks__/@fluentui/utilities.ts b/packages/react/__mocks__/@fluentui/utilities.ts index e88d4290a834c..e0c338e3413dc 100644 --- a/packages/react/__mocks__/@fluentui/utilities.ts +++ b/packages/react/__mocks__/@fluentui/utilities.ts @@ -41,6 +41,12 @@ class MockAsync extends Async { super.dispose(); } + + protected _logError(e: any) { + super._logError(e); + // Don't eat errors thrown from async callbacks + throw e; + } } export { MockAsync as Async }; diff --git a/packages/react/src/components/ComboBox/ComboBox.test.tsx b/packages/react/src/components/ComboBox/ComboBox.test.tsx index 4986bf030d389..386513426c66a 100644 --- a/packages/react/src/components/ComboBox/ComboBox.test.tsx +++ b/packages/react/src/components/ComboBox/ComboBox.test.tsx @@ -80,7 +80,7 @@ describe('ComboBox', () => { it(`renders`, () => { safeCreate(, wrapper => { - expect(wrapper.root).toBeDefined(); + expect(wrapper.root).toBeTruthy(); }); }); @@ -693,7 +693,7 @@ describe('ComboBox', () => { }); const callout = container.root.find(node => node.props?.className?.split?.(' ').includes?.('ms-Callout')); - expect(callout).toBeDefined(); + expect(callout).toBeTruthy(); expect(callout.props.className.includes('ms-ComboBox-callout')).toBeTruthy(); expect(callout.props.className.includes('foo')).toBeTruthy(); }, @@ -922,7 +922,7 @@ describe('ComboBox', () => { // Find menu const calloutBeforeOpen = findNodeWithClass(container, 'ms-Callout'); - expect(calloutBeforeOpen).toBeDefined(); + expect(calloutBeforeOpen).toBeTruthy(); expect(calloutBeforeOpen?.props?.className?.includes?.('ms-ComboBox-callout')).toBeTruthy(); // Open combobox @@ -940,7 +940,7 @@ describe('ComboBox', () => { // Ensure menu is still there const calloutAfterClose = findNodeWithClass(container, 'ms-Callout'); - expect(calloutAfterClose).toBeDefined(); + expect(calloutAfterClose).toBeTruthy(); expect(calloutBeforeOpen?.props?.className?.includes?.('ms-ComboBox-callout')).toBeTruthy(); }, ); diff --git a/packages/react/src/components/DetailsList/DetailsList.test.tsx b/packages/react/src/components/DetailsList/DetailsList.test.tsx index 7ea396bc76fa1..f1bfd502b5d57 100644 --- a/packages/react/src/components/DetailsList/DetailsList.test.tsx +++ b/packages/react/src/components/DetailsList/DetailsList.test.tsx @@ -168,7 +168,7 @@ describe('DetailsList', () => { it('focuses row by index', () => { jest.useFakeTimers(); - let component: any; + let component: IDetailsList | null; safeMount( { onShouldVirtualize={() => false} />, () => { - expect(component).toBeDefined(); - (component as IDetailsList).focusIndex(2); + expect(component).toBeTruthy(); + component!.focusIndex(2); setTimeout(() => { expect( (document.activeElement as HTMLElement).querySelector('[data-automationid=DetailsRowCell]')!.textContent, @@ -330,7 +330,7 @@ describe('DetailsList', () => { jest.useFakeTimers(); - let component: any; + let component: IDetailsList | null; safeMount( { getCellValueKey={getCellValueKey} />, () => { - expect(component).toBeDefined(); - (component as IDetailsList).focusIndex(3); + expect(component).toBeTruthy(); + component!.focusIndex(3); jest.runOnlyPendingTimers(); expect( (document.activeElement as HTMLElement).querySelector('[data-automationid=DetailsRowCell]')!.textContent, @@ -379,7 +379,7 @@ describe('DetailsList', () => { onShouldVirtualize={() => false} />, (wrapper: ReactWrapper) => { - expect(component).toBeDefined(); + expect(component).toBeTruthy(); component.setState({ focusedItemIndex: 3 }); setTimeout(() => { expect(component.state.focusedItemIndex).toEqual(3); diff --git a/packages/utilities/src/Async.ts b/packages/utilities/src/Async.ts index 74185e4e09be7..c457053a6ac4a 100644 --- a/packages/utilities/src/Async.ts +++ b/packages/utilities/src/Async.ts @@ -109,9 +109,7 @@ export class Async { } callback.apply(this._parent); } catch (e) { - if (this._onErrorHandler) { - this._onErrorHandler(e); - } + this._logError(e); } }, duration); diff --git a/packages/utilities/src/BaseComponent.test.tsx b/packages/utilities/src/BaseComponent.test.tsx index 49215a39f1c44..74bb466872515 100644 --- a/packages/utilities/src/BaseComponent.test.tsx +++ b/packages/utilities/src/BaseComponent.test.tsx @@ -20,6 +20,6 @@ describe('BaseComponent', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any ) as any; - expect(component.root).toBeDefined(); + expect(component.root).toBeTruthy(); }); }); diff --git a/scripts/jest/jest-setup.js b/scripts/jest/jest-setup.js index 5ad4372a938de..f82e510743c67 100644 --- a/scripts/jest/jest-setup.js +++ b/scripts/jest/jest-setup.js @@ -14,10 +14,37 @@ function customError(type, ...args) { consoleError(...args); } - if (args.length === 1 && typeof args[0] === 'object' && args[0].stack) { + let processedArgs = args; + // console.log messages can include substitution tokens such as %s (React uses this). + // Attempt a very naive replacement of those tokens when throwing the error. + if (args.length > 1 && typeof args[0] === 'string' && /%[dfioOs]/.test(args[0])) { + let unprocessedArgs = args.slice(1); + let message = /** @type {string} */ (args[0]); + if (message.startsWith('Warning: An update to %s inside a test was not wrapped in act')) { + // trim less-useful parts of this message from the exception (they'll still be in the log) + message = message.replace( + /^Warning: [\s\S]+would see in the browser\./, + `Warning: An update to ${args[1]} inside a test was not wrapped in act(...).`, + ); + unprocessedArgs = []; + } + processedArgs = [ + message.replace(/%[dfioOs]/g, () => { + const nextArg = unprocessedArgs.shift(); + return nextArg === undefined + ? '' + : nextArg && typeof nextArg === 'object' + ? JSON.stringify(nextArg, null, 2) // avoid "[object Object]" + : nextArg; // everything else just use default formatting + }), + ]; + processedArgs.push(...unprocessedArgs); + } + + if (processedArgs.length === 1 && typeof processedArgs[0] === 'object' && processedArgs[0].stack) { // If the "message" was an exception, re-throw it to get the full stack trace - throw args[0]; + throw processedArgs[0]; } else { - throw new Error(`[console.${type}] ${args.join(' ')}`); + throw new Error(`[console.${type}] ${processedArgs.join(' ')}`); } }