utils.test.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import { DataTypes, sql } from '@sequelize/core';
  2. import { toDefaultValue } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dialect.js';
  3. import { mapFinderOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/format.js';
  4. import {
  5. cloneDeep,
  6. defaults,
  7. flattenObjectDeep,
  8. merge,
  9. } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/object.js';
  10. import {
  11. pluralize,
  12. singularize,
  13. underscoredIf,
  14. } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/string.js';
  15. import { expect } from 'chai';
  16. import { allowDeprecationsInSuite, sequelize } from '../../support';
  17. const dialect = sequelize.dialect;
  18. describe('Utils', () => {
  19. describe('underscore', () => {
  20. describe('underscoredIf', () => {
  21. it('is defined', () => {
  22. expect(underscoredIf).to.be.ok;
  23. });
  24. it('underscores if second param is true', () => {
  25. expect(underscoredIf('fooBar', true)).to.equal('foo_bar');
  26. });
  27. it("doesn't underscore if second param is false", () => {
  28. expect(underscoredIf('fooBar', false)).to.equal('fooBar');
  29. });
  30. });
  31. });
  32. describe('cloneDeep', () => {
  33. it('should clone objects', () => {
  34. const obj = { foo: 1 };
  35. const clone = cloneDeep(obj);
  36. expect(clone).to.deep.equal(obj);
  37. expect(clone).to.not.equal(obj);
  38. });
  39. it('should clone nested objects', () => {
  40. const obj = { foo: { bar: 1 } };
  41. const clone = cloneDeep(obj);
  42. expect(clone).to.deep.equal(obj);
  43. expect(clone).to.not.equal(obj);
  44. });
  45. it('clones sql expression builders', () => {
  46. const obj = [
  47. sql`literal test`,
  48. sql.where({ foo: 'bar' }),
  49. sql.col('foo'),
  50. sql.unquote('foo'),
  51. sql.cast('foo', 'bar'),
  52. sql.fn('foo', 'bar'),
  53. sql.attribute('foo'),
  54. sql.identifier('foo'),
  55. sql.jsonPath(sql.attribute('foo'), ['foo']),
  56. sql.list(['a', 'b']),
  57. ];
  58. const clone = cloneDeep(obj);
  59. expect(clone).to.deep.equal(obj);
  60. expect(clone).to.not.equal(obj);
  61. });
  62. it('should not call clone methods on plain objects', () => {
  63. expect(() => {
  64. cloneDeep({
  65. clone() {
  66. throw new Error('clone method called');
  67. },
  68. });
  69. }).to.not.throw();
  70. });
  71. it('should not call clone methods on arrays', () => {
  72. expect(() => {
  73. const arr: unknown[] = [];
  74. // @ts-expect-error -- type error normal, you're not supposed to add methods to array instances.
  75. arr.clone = function clone() {
  76. throw new Error('clone method called');
  77. };
  78. cloneDeep(arr);
  79. }).to.not.throw();
  80. });
  81. });
  82. describe('inflection', () => {
  83. it('should pluralize/singularize words correctly', () => {
  84. expect(pluralize('buy')).to.equal('buys');
  85. expect(pluralize('holiday')).to.equal('holidays');
  86. expect(pluralize('days')).to.equal('days');
  87. expect(pluralize('status')).to.equal('statuses');
  88. expect(singularize('status')).to.equal('status');
  89. });
  90. });
  91. describe('flattenObjectDeep', () => {
  92. it('should return the value if it is not an object', () => {
  93. const value = 'non-object';
  94. const returnedValue = flattenObjectDeep(value);
  95. expect(returnedValue).to.equal(value);
  96. });
  97. it('should return correctly if values are null', () => {
  98. const value = {
  99. name: 'John',
  100. address: {
  101. street: 'Fake St. 123',
  102. city: null,
  103. coordinates: {
  104. longitude: 55.677_962_7,
  105. latitude: 12.596_431_3,
  106. },
  107. },
  108. };
  109. const returnedValue = flattenObjectDeep(value);
  110. expect(returnedValue).to.deep.equal({
  111. name: 'John',
  112. 'address.street': 'Fake St. 123',
  113. 'address.city': null,
  114. 'address.coordinates.longitude': 55.677_962_7,
  115. 'address.coordinates.latitude': 12.596_431_3,
  116. });
  117. });
  118. });
  119. describe('merge', () => {
  120. it('does not clone sequelize models', () => {
  121. const User = sequelize.define('user');
  122. const merged = merge({}, { include: [{ model: User }] });
  123. const merged2 = merge({}, { user: User });
  124. // @ts-expect-error -- TODO: merge's return type is bad, to improve
  125. expect(merged.include[0].model).to.equal(User);
  126. // @ts-expect-error -- see above
  127. expect(merged2.user).to.equal(User);
  128. });
  129. });
  130. describe('toDefaultValue', () => {
  131. allowDeprecationsInSuite(['SEQUELIZE0026']);
  132. it('return uuid v1', () => {
  133. expect(
  134. /^[\da-z-]{36}$/.test(
  135. toDefaultValue(new DataTypes.UUIDV1().toDialectDataType(dialect)) as string,
  136. ),
  137. ).to.equal(true);
  138. });
  139. it('return uuid v4', () => {
  140. expect(
  141. /^[\da-z-]{36}/.test(
  142. toDefaultValue(new DataTypes.UUIDV4().toDialectDataType(dialect)) as string,
  143. ),
  144. ).to.equal(true);
  145. });
  146. it('return now', () => {
  147. expect(
  148. Object.prototype.toString.call(
  149. toDefaultValue(new DataTypes.NOW().toDialectDataType(dialect)),
  150. ),
  151. ).to.equal('[object Date]');
  152. });
  153. it('return plain string', () => {
  154. expect(toDefaultValue('Test')).to.equal('Test');
  155. });
  156. it('return plain object', () => {
  157. expect(toDefaultValue({})).to.deep.equal({});
  158. });
  159. });
  160. describe('defaults', () => {
  161. it('defaults normal object', () => {
  162. expect(defaults({ a: 1, c: 3 }, { b: 2 }, { c: 4, d: 4 })).to.eql({
  163. a: 1,
  164. b: 2,
  165. c: 3,
  166. d: 4,
  167. });
  168. });
  169. it('defaults symbol keys', () => {
  170. expect(
  171. defaults(
  172. { a: 1, [Symbol.for('eq')]: 3 },
  173. { b: 2 },
  174. { [Symbol.for('eq')]: 4, [Symbol.for('ne')]: 4 },
  175. ),
  176. ).to.eql({
  177. a: 1,
  178. b: 2,
  179. [Symbol.for('eq')]: 3,
  180. [Symbol.for('ne')]: 4,
  181. });
  182. });
  183. });
  184. describe('mapFinderOptions', () => {
  185. it('virtual attribute dependencies', () => {
  186. const User = sequelize.define('User', {
  187. createdAt: {
  188. type: DataTypes.DATE,
  189. field: 'created_at',
  190. },
  191. active: {
  192. type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['createdAt']),
  193. },
  194. });
  195. expect(mapFinderOptions({ attributes: ['active'] }, User).attributes).to.eql([
  196. ['created_at', 'createdAt'],
  197. ]);
  198. });
  199. it('multiple calls', () => {
  200. const User = sequelize.define('User', {
  201. createdAt: {
  202. type: DataTypes.DATE,
  203. field: 'created_at',
  204. },
  205. active: {
  206. type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['createdAt']),
  207. },
  208. });
  209. expect(
  210. mapFinderOptions(
  211. // @ts-expect-error -- TODO: improve mapFinderOptions typing
  212. mapFinderOptions(
  213. {
  214. attributes: ['active'],
  215. },
  216. User,
  217. ),
  218. User,
  219. ).attributes,
  220. ).to.eql([['created_at', 'createdAt']]);
  221. });
  222. });
  223. });