UNPKG

react-oc

Version:

A React component that allows OpenComponents to operate within a react application.

711 lines (637 loc) 30.2 kB
"use strict"; require("jest-plugin-console-matchers/setup"); var _react = _interopRequireDefault(require("react")); var _reactDom = _interopRequireDefault(require("react-dom")); var _server = _interopRequireDefault(require("react-dom/server")); var _bluebird = _interopRequireDefault(require("bluebird")); var _jquery = _interopRequireDefault(require("jquery")); var _reactHelpers = require("./__test__/react-helpers"); var _OpenComponentsContext = require("./OpenComponentsContext"); var _OCContext = require("./OCContext"); var _OpenComponent = require("./OpenComponent"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } describe('<OpenComponent />', function () { describe('When not mounted within a <ComponentContext />', function () { it('throws an error', function () { var node = document.createElement('div'); return expect((0, _reactHelpers.renderAsync)(_react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component" }), node)).rejects.toThrow(/OpenComponent must be nested within a <ComponentContext \/>/); }); }); var oc = {}; var fakeResponse = '<oc-component src="http://localhost/my-component"></oc-component>'; var baseContext = { oc: oc, baseUrl: 'http://localhost/', getElements: function getElements() {}, getHtml: function getHtml() {}, saveElements: function saveElements() {} }; beforeEach(function () { oc.build = jest.fn().mockImplementation(function () { return fakeResponse; }); oc.$ = _jquery.default; oc.renderNestedComponent = jest.fn(function (_, cb) { return cb(); }); }); it('throws when no name is provided', function () { var node = document.createElement('div'); return expect((0, _reactHelpers.renderAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext) }, _react.default.createElement(_OpenComponent.OpenComponent, null)), node)).rejects.toThrow(/Mandatory prop 'name' is missing./); }); it('throws when no baseUrl is provided in context', function () { var node = document.createElement('div'); var baseUrl = baseContext.baseUrl, rest = _objectWithoutProperties(baseContext, ["baseUrl"]); return expect((0, _reactHelpers.renderAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, rest) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component" })), node)).rejects.toThrow(/<OpenComponentsContext> must have a defined 'baseUrl' prop to use this component/); }); it('should apply the given id to a container div', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee() { var node; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: node = document.createElement('div'); _context.next = 3; return (0, _reactHelpers.renderAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext) }, _react.default.createElement(_OpenComponent.OpenComponent, { id: "my-unique-id", name: "my-component" })), node); case 3: expect(node.childNodes[0].id).toBe('my-unique-id'); case 4: case "end": return _context.stop(); } } }, _callee, this); }))); it('should apply the given className to a container div', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee2() { var node; return regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: node = document.createElement('div'); _context2.next = 3; return (0, _reactHelpers.renderAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext) }, _react.default.createElement(_OpenComponent.OpenComponent, { className: "my-class", name: "my-component" })), node); case 3: expect(node.childNodes[0].className).toBe('my-class'); case 4: case "end": return _context2.stop(); } } }, _callee2, this); }))); it('should call oc.build with relevant parameters', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee3() { var node, parameters; return regeneratorRuntime.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: node = document.createElement('div'); parameters = { hello: 'world' }; _context3.next = 4; return (0, _reactHelpers.renderAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { oc: oc, lang: 'en-GB' }) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component", version: "1.X.X", parameters: parameters })), node); case 4: expect(oc.build).toBeCalledWith({ name: 'my-component', version: '1.X.X', baseUrl: 'http://localhost/', lang: 'en-GB', parameters: parameters }); case 5: case "end": return _context3.stop(); } } }, _callee3, this); }))); it('should call oc.build with lang from component over context', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee4() { var node; return regeneratorRuntime.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: node = document.createElement('div'); _context4.next = 3; return (0, _reactHelpers.renderAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { oc: oc, lang: 'en-GB' }) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component", lang: "en-US" })), node); case 3: expect(oc.build).toBeCalledWith(expect.objectContaining({ lang: 'en-US' })); case 4: case "end": return _context4.stop(); } } }, _callee4, this); }))); it('should render the response of oc.build', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee5() { var node, parameters; return regeneratorRuntime.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: node = document.createElement('div'); parameters = { hello: 'world' }; _context5.next = 4; return (0, _reactHelpers.renderAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { oc: oc }) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component", version: "1.X.X", lang: "en-GB", parameters: parameters })), node); case 4: expect(node.innerHTML).toContain(fakeResponse); case 5: case "end": return _context5.stop(); } } }, _callee5, this); }))); it('should call oc.renderNestedComponent with a jquery element containing the response of oc.build', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee6() { var node, parameters; return regeneratorRuntime.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: node = document.createElement('div'); parameters = { hello: 'world' }; _context6.next = 4; return (0, _reactHelpers.renderAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { oc: oc }) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component", version: "1.X.X", lang: "en-GB", parameters: parameters })), node); case 4: expect(oc.renderNestedComponent).toBeCalledWith(expect.objectContaining({ 0: expect.objectContaining({ outerHTML: expect.stringContaining(fakeResponse) }) }), expect.anything() //callback function ); case 5: case "end": return _context6.stop(); } } }, _callee6, this); }))); describe('when a given a captureAs prop', function () { it('calls saveElements on context with the captureAs value and oc-component element after oc finishes rendering', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee7() { var node, saveElements, getElements, fakeResponse; return regeneratorRuntime.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: node = document.createElement('div'); saveElements = jest.fn(); getElements = jest.fn(); fakeResponse = '<oc-component src="http://localhost/my-component"></oc-component>'; oc.build.mockImplementation(function () { return fakeResponse; }); _context7.next = 7; return (0, _reactHelpers.renderAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { saveElements: saveElements, getElements: getElements, oc: oc }) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component", captureAs: "my-component-1" })), node); case 7: expect(saveElements).toBeCalledWith('my-component-1', [expect.objectContaining({ outerHTML: fakeResponse })]); case 8: case "end": return _context7.stop(); } } }, _callee7, this); }))); it('does not allow dangerouslySetInnerHtml remove existing markup', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee8() { var node, elements, saveElements, getElements, fakeResponse, modifiedFakeResponse, RenderTwice; return regeneratorRuntime.wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { case 0: /** * This test protects a bug fix from regression. * * Context: React may call the render function at any time, and in some cases, without * using the lifecycle methods (shouldComponentUpdate and componentDidUpdate). * This combined with the dangerouslySetInnerHtml property, if react detects that the * render method returned anything different, it may choose to update the browser DOM. * * This test triggers the behaviour described and ensures that even though oc will modify * the originally specified markup, the render method does not cause React to undo this. */ node = document.createElement('div'); saveElements = jest.fn(function (key, els) { return elements = els; }); getElements = jest.fn(function () { return elements; }); fakeResponse = '<oc-component src="http://localhost/my-component"></oc-component>'; modifiedFakeResponse = '<oc-component src="http://localhost/my-component">hello world</oc-component>'; oc.build.mockImplementation(function () { return fakeResponse; }); oc.renderNestedComponent.mockImplementation(function (component, cb) { component[0].innerHTML = 'hello world'; cb(); }); RenderTwice = /*#__PURE__*/ function (_React$Component) { _inherits(RenderTwice, _React$Component); function RenderTwice() { _classCallCheck(this, RenderTwice); return _possibleConstructorReturn(this, _getPrototypeOf(RenderTwice).apply(this, arguments)); } _createClass(RenderTwice, [{ key: "render", value: function render() { var _this = this; this.state = {}; setTimeout(function () { _this.setState({ renderAgain: true }); }, 3); return _react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { saveElements: saveElements, getElements: getElements, oc: oc }) }, this.state.renderAgain, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component", captureAs: "my-component-1" })); } }]); return RenderTwice; }(_react.default.Component); _context8.next = 10; return (0, _reactHelpers.renderAsync)(_react.default.createElement(RenderTwice, null), node); case 10: _context8.next = 12; return _bluebird.default.delay(5); case 12: expect(node.innerHTML).toContain(modifiedFakeResponse); case 13: case "end": return _context8.stop(); } } }, _callee8, this); }))); describe('when calling context.getElements with the captureAs prop returns a html element', function () { it('calls getElements with the captureAs key', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee9() { var node, element, getElements; return regeneratorRuntime.wrap(function _callee9$(_context9) { while (1) { switch (_context9.prev = _context9.next) { case 0: node = document.createElement('div'); element = document.createElement('span'); getElements = jest.fn().mockImplementation(function (key) { return [element]; }); _context9.next = 5; return (0, _reactHelpers.renderAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { getElements: getElements, oc: oc }) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component", captureAs: "my-component-1" })), node); case 5: expect(getElements).toBeCalledWith('my-component-1'); case 6: case "end": return _context9.stop(); } } }, _callee9, this); }))); it('injects the elements into the container', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee10() { var node, prevNode, elements, getElements, div, i; return regeneratorRuntime.wrap(function _callee10$(_context10) { while (1) { switch (_context10.prev = _context10.next) { case 0: node = document.createElement('div'); prevNode = document.createElement('span'); prevNode.innerHTML = '<span>span</span>hello<div>div</div>'; elements = _toConsumableArray(prevNode.childNodes); getElements = jest.fn().mockImplementation(function (key) { return elements; }); _context10.next = 7; return (0, _reactHelpers.renderAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { getElements: getElements, oc: oc }) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component", captureAs: "my-component-1" })), node); case 7: div = node.childNodes[0]; expect(div.childNodes.length).toBe(elements.length); for (i = 0; i < elements.length; i++) { // using toBe ensures that it is the same element with // all event handlers etc. in-tact. expect(div.childNodes[i]).toBe(elements[i]); } case 10: case "end": return _context10.stop(); } } }, _callee10, this); }))); }); }); it('should throw an error when context does not have an oc property', function () { var node = document.createElement('div'); var oc = baseContext.oc, rest = _objectWithoutProperties(baseContext, ["oc"]); return expect((0, _reactHelpers.renderAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, rest) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "header" })), node)).rejects.toThrow(/clientOc not defined/); }); describe('universal support', function () { it('should not throw when no baseUrl is provided in context when server side rendering', function () { var node = document.createElement('div'); var baseUrl = baseContext.baseUrl, rest = _objectWithoutProperties(baseContext, ["baseUrl"]); return expect(function () { return _server.default.renderToString(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, rest) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component" }))); }).not.toThrow(); }); it('should not throw an error when context does not have an oc property but using server side rendering', function () { var oc = baseContext.oc, rest = _objectWithoutProperties(baseContext, ["oc"]); var app = _react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, rest) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "header" })); expect(function () { return _server.default.renderToString(app); }).not.toThrow(); }); it('should render an empty div when server side rendering without oc', function () { var node = document.createElement('div'); node.innerHTML = _server.default.renderToString(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { oc: undefined }) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component" }))); expect(node.innerHTML).toBe('<div></div>'); }); it('should change the empty div markup after hydrating over server rendered markup', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee11() { var node, existingMarkup; return regeneratorRuntime.wrap(function _callee11$(_context11) { while (1) { switch (_context11.prev = _context11.next) { case 0: node = document.createElement('div'); node.innerHTML = _server.default.renderToString(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { oc: undefined }) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component" }))); existingMarkup = node.innerHTML; _context11.next = 5; return (0, _reactHelpers.hydrateAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component" })), node); case 5: expect(node.innerHTML).not.toBe(existingMarkup); case 6: case "end": return _context11.stop(); } } }, _callee11, this); }))); it('should call oc.build with the correct parameters after hydrating', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee12() { var node; return regeneratorRuntime.wrap(function _callee12$(_context12) { while (1) { switch (_context12.prev = _context12.next) { case 0: node = document.createElement('div'); node.innerHTML = _server.default.renderToString(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { oc: undefined, lang: 'en-GB' }) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component" }))); _context12.next = 4; return (0, _reactHelpers.hydrateAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { oc: oc, lang: 'en-GB' }) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component" })), node); case 4: expect(oc.build).toHaveBeenLastCalledWith({ baseUrl: baseContext.baseUrl, lang: 'en-GB', name: 'my-component' }); case 5: case "end": return _context12.stop(); } } }, _callee12, this); }))); it('should call oc.renderNestedComponent with a jquery element containing the response of oc.build after hydrating', /*#__PURE__*/ _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee13() { var node; return regeneratorRuntime.wrap(function _callee13$(_context13) { while (1) { switch (_context13.prev = _context13.next) { case 0: node = document.createElement('div'); node.innerHTML = _server.default.renderToString(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext, { oc: undefined }) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component" }))); _context13.next = 4; return (0, _reactHelpers.hydrateAsync)(_react.default.createElement(_OCContext.OCContext.Provider, { value: _objectSpread({}, baseContext) }, _react.default.createElement(_OpenComponent.OpenComponent, { name: "my-component" })), node); case 4: expect(oc.renderNestedComponent).toBeCalledWith(expect.objectContaining({ 0: expect.objectContaining({ outerHTML: expect.stringContaining(fakeResponse) }) }), expect.anything() //callback function ); case 5: case "end": return _context13.stop(); } } }, _callee13, this); }))); }); }); //# sourceMappingURL=OpenComponent.test.js.map