utils.spec.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. import {mat3, vec2} from 'gl-matrix';
  2. import {
  3. createProgram,
  4. createShader,
  5. ELLIPSIS,
  6. findRangeBinarySearch,
  7. getContext,
  8. makeProjectionMatrix,
  9. Rect,
  10. Transform,
  11. trimTextCenter,
  12. } from 'sentry/utils/profiling/gl/utils';
  13. describe('makeProjectionMatrix', () => {
  14. it('should return a projection matrix', () => {
  15. // prettier-ignore
  16. expect(makeProjectionMatrix(1024, 768)).toEqual(mat3.fromValues(
  17. 2/1024, 0, 0,
  18. -0, -2/768, -0,
  19. -1,1,1
  20. ));
  21. });
  22. });
  23. describe('getContext', () => {
  24. it('throws if it cannot retrieve context', () => {
  25. expect(() =>
  26. // @ts-ignore partial canvas mock
  27. getContext({getContext: jest.fn().mockImplementationOnce(() => null)}, 'webgl')
  28. ).toThrow();
  29. expect(() =>
  30. // @ts-ignore partial canvas mock
  31. getContext({getContext: jest.fn().mockImplementationOnce(() => null)}, '2d')
  32. ).toThrow();
  33. });
  34. it('returns ctx', () => {
  35. const ctx = {};
  36. expect(
  37. // @ts-ignore partial canvas mock
  38. getContext({getContext: jest.fn().mockImplementationOnce(() => ctx)}, 'webgl')
  39. ).toBe(ctx);
  40. });
  41. });
  42. describe('createProgram', () => {
  43. it('throws if it fails to create a program', () => {
  44. const ctx: Partial<WebGLRenderingContext> = {
  45. createProgram: jest.fn().mockImplementation(() => {
  46. return null;
  47. }),
  48. };
  49. // @ts-ignore this is a partial mock
  50. expect(() => createProgram(ctx, {}, {})).toThrow('Could not create program');
  51. });
  52. it('attaches both shaders and links program', () => {
  53. const program = {};
  54. const ctx: Partial<WebGLRenderingContext> = {
  55. createProgram: jest.fn().mockImplementation(() => {
  56. return program;
  57. }),
  58. getProgramParameter: jest.fn().mockImplementation(() => program),
  59. linkProgram: jest.fn(),
  60. attachShader: jest.fn(),
  61. };
  62. const vertexShader = {};
  63. const fragmentShader = {};
  64. // @ts-ignore this is a partial mock
  65. createProgram(ctx, vertexShader, fragmentShader);
  66. expect(ctx.createProgram).toHaveBeenCalled();
  67. expect(ctx.linkProgram).toHaveBeenCalled();
  68. expect(ctx.attachShader).toHaveBeenCalledWith(program, vertexShader);
  69. expect(ctx.attachShader).toHaveBeenCalledWith(program, fragmentShader);
  70. });
  71. it('deletes the program if compiling fails', () => {
  72. const program = {};
  73. const ctx: Partial<WebGLRenderingContext> = {
  74. createProgram: jest.fn().mockImplementation(() => {
  75. return program;
  76. }),
  77. deleteProgram: jest.fn(),
  78. getProgramParameter: jest.fn().mockImplementation(() => 0),
  79. linkProgram: jest.fn(),
  80. attachShader: jest.fn(),
  81. };
  82. const vertexShader = {};
  83. const fragmentShader = {};
  84. // @ts-ignore this is a partial mock
  85. expect(() => createProgram(ctx, vertexShader, fragmentShader)).toThrow();
  86. expect(ctx.createProgram).toHaveBeenCalled();
  87. expect(ctx.linkProgram).toHaveBeenCalled();
  88. expect(ctx.attachShader).toHaveBeenCalledWith(program, vertexShader);
  89. expect(ctx.attachShader).toHaveBeenCalledWith(program, fragmentShader);
  90. expect(ctx.deleteProgram).toHaveBeenCalledWith(program);
  91. });
  92. });
  93. describe('createShader', () => {
  94. it('fails to create', () => {
  95. const ctx: Partial<WebGLRenderingContext> = {
  96. createShader: jest.fn().mockImplementationOnce(() => null),
  97. };
  98. const type = 0;
  99. // @ts-ignore this is a partial mock
  100. expect(() => createShader(ctx, type, '')).toThrow();
  101. expect(ctx.createShader).toHaveBeenLastCalledWith(type);
  102. });
  103. it('successfully compiles', () => {
  104. const shader: WebGLShader = {};
  105. const type = 0;
  106. const shaderSource = `vec4(1.0, 0.0, 0.0, 1.0)`;
  107. const ctx: Partial<WebGLRenderingContext> = {
  108. createShader: jest.fn().mockImplementation(() => shader),
  109. shaderSource: jest.fn(),
  110. compileShader: jest.fn(),
  111. getShaderParameter: jest.fn().mockImplementation(() => 1),
  112. COMPILE_STATUS: 1,
  113. };
  114. // @ts-ignore this is a partial mock
  115. expect(() => createShader(ctx, type, shaderSource)).not.toThrow();
  116. // @ts-ignore this is a partial mock
  117. expect(createShader(ctx, type, shaderSource)).toBe(shader);
  118. expect(ctx.shaderSource).toHaveBeenLastCalledWith(shader, shaderSource);
  119. expect(ctx.getShaderParameter).toHaveBeenLastCalledWith(shader, ctx.COMPILE_STATUS);
  120. });
  121. it('deletes shader if compilation fails', () => {
  122. const shader: WebGLShader = {};
  123. const type = 0;
  124. const shaderSource = `vec4(1.0, 0.0, 0.0, 1.0)`;
  125. const ctx: Partial<WebGLRenderingContext> = {
  126. createShader: jest.fn().mockImplementation(() => shader),
  127. shaderSource: jest.fn(),
  128. compileShader: jest.fn(),
  129. getShaderParameter: jest.fn().mockImplementation(() => 0),
  130. deleteShader: jest.fn(),
  131. COMPILE_STATUS: 0,
  132. };
  133. // @ts-ignore this is a partial mock
  134. expect(() => createShader(ctx, type, shaderSource)).toThrow(
  135. 'Failed to compile shader'
  136. );
  137. });
  138. });
  139. describe('Transform', () => {
  140. it('betweenRect', () => {
  141. expect(Transform.betweenRect(new Rect(2, 3, 4, 5), new Rect(1, 2, 10, 15))).toEqual(
  142. new Rect(1, 2, 2.5, 3)
  143. );
  144. });
  145. });
  146. describe('Rect', () => {
  147. it('initializes an empty rect as 0 width and height rect at 0,0 origin', () => {
  148. expect(Rect.Empty()).toEqual(new Rect(0, 0, 0, 0));
  149. expect(Rect.Empty().isEmpty()).toBe(true);
  150. });
  151. it('clones rect', () => {
  152. const a = new Rect(1, 2, 3, 4);
  153. const b = Rect.From(a);
  154. expect(b.equals(a)).toBe(true);
  155. });
  156. it('getters return correct values', () => {
  157. const rect = new Rect(1, 2, 3, 4);
  158. expect(rect.x).toBe(1);
  159. expect(rect.y).toBe(2);
  160. expect(rect.width).toBe(3);
  161. expect(rect.height).toBe(4);
  162. expect(rect.left).toBe(rect.x);
  163. expect(rect.right).toBe(rect.left + rect.width);
  164. expect(rect.top).toBe(rect.y);
  165. expect(rect.bottom).toBe(rect.y + rect.height);
  166. });
  167. describe('collision', () => {
  168. it('containsX', () => {
  169. expect(new Rect(0, 0, 1, 1).containsX(vec2.fromValues(0.5, 0))).toBe(true);
  170. // when we are exactly on the edge
  171. expect(new Rect(0, 0, 1, 1).containsX(vec2.fromValues(0, 0))).toBe(true);
  172. expect(new Rect(0, 0, 1, 1).containsX(vec2.fromValues(1, 0))).toBe(true);
  173. // when we are outside the rect
  174. expect(new Rect(0, 0, 1, 1).containsX(vec2.fromValues(-0.5, 0))).toBe(false);
  175. expect(new Rect(0, 0, 1, 1).containsX(vec2.fromValues(1.5, 0))).toBe(false);
  176. });
  177. it('containsY', () => {
  178. expect(new Rect(0, 0, 1, 1).containsY(vec2.fromValues(0, 0.5))).toBe(true);
  179. // when we are exactly on the edge
  180. expect(new Rect(0, 0, 1, 1).containsY(vec2.fromValues(0, 0))).toBe(true);
  181. expect(new Rect(0, 0, 1, 1).containsY(vec2.fromValues(0, 1))).toBe(true);
  182. // when we are outside the rect
  183. expect(new Rect(0, 0, 1, 1).containsY(vec2.fromValues(0, -0.5))).toBe(false);
  184. expect(new Rect(0, 0, 1, 1).containsY(vec2.fromValues(0, 1.5))).toBe(false);
  185. });
  186. it('contains', () => {
  187. expect(new Rect(0, 0, 1, 1).contains(vec2.fromValues(0.5, 0.5))).toBe(true);
  188. expect(new Rect(0, 0, 1, 1).contains(vec2.fromValues(1.5, 1.5))).toBe(false);
  189. expect(new Rect(0, 0, 1, 1).contains(vec2.fromValues(-0.5, -0.5))).toBe(false);
  190. });
  191. it('containsRect', () => {
  192. expect(new Rect(0, 0, 1, 1).containsRect(new Rect(0.1, 0.1, 0.1, 0.1))).toBe(true);
  193. });
  194. it('overlaps', () => {
  195. expect(new Rect(0, 0, 1, 1).overlaps(new Rect(-1, -1, 2, 2))).toBe(true);
  196. // we are exactly on the edge
  197. expect(new Rect(0, 0, 1, 1).overlaps(new Rect(1, 1, 1, 1))).toBe(true);
  198. expect(new Rect(0, 0, 1, 1).overlaps(new Rect(2, 1, 1, 1))).toBe(false);
  199. expect(new Rect(0, 0, 1, 1).overlaps(new Rect(-1, -1, 1, 1))).toBe(true);
  200. });
  201. it('hasIntersectionWidth', () => {
  202. expect(new Rect(0, 0, 1, 1).hasIntersectionWith(new Rect(1, 1, 2, 2))).toBe(false);
  203. expect(new Rect(0, 0, 1, 1).hasIntersectionWith(new Rect(-1, -1, 2, 2))).toBe(true);
  204. });
  205. });
  206. it('withHeight', () => {
  207. expect(new Rect(0, 0, 1, 1).withHeight(2).height).toBe(2);
  208. });
  209. it('withWidth', () => {
  210. expect(new Rect(0, 0, 1, 1).withWidth(2).width).toBe(2);
  211. });
  212. it('toBounds', () => {
  213. expect(new Rect(1, 0, 2, 2).toBounds()).toEqual([1, 0, 3, 2]);
  214. });
  215. it('toArray', () => {
  216. expect(new Rect(0, 0, 1, 1).toArray()).toEqual([0, 0, 1, 1]);
  217. });
  218. it('between', () => {
  219. expect(new Rect(1, 1, 2, 4).between(new Rect(2, 2, 4, 10))).toEqual(
  220. new Rect(2, 2, 2, 2.5)
  221. );
  222. });
  223. it('toMatrix', () => {
  224. expect(new Rect(0.5, 1, 2, 3).toMatrix()).toEqual(
  225. mat3.fromValues(2, 0, 0, 0, 3, 0, 0.5, 1, 1)
  226. );
  227. });
  228. it('notEqualTo', () => {
  229. expect(new Rect(0, 0, 1, 1).notEqualTo(new Rect(0, 0, 1, 1))).toBe(false);
  230. expect(new Rect(0, 0, 1, 1).notEqualTo(new Rect(0, 0, 1, 2))).toBe(true);
  231. });
  232. describe('transforms', () => {
  233. it('transformRect', () => {
  234. // prettier-ignore
  235. // Scale (10,20),translate by (3, 4)
  236. const matrix = mat3.fromValues(
  237. 10,0,0,
  238. 0,20,0,
  239. 3,4,0,
  240. )
  241. expect(new Rect(1, 1, 1, 1).transformRect(matrix)).toEqual(
  242. new Rect(13, 24, 10, 20)
  243. );
  244. });
  245. it('translateX', () => {
  246. expect(new Rect(0, 0, 1, 1).translateX(1).x).toBe(1);
  247. });
  248. it('translateY', () => {
  249. expect(new Rect(0, 0, 1, 1).translateY(1).y).toBe(1);
  250. });
  251. it('translate', () => {
  252. expect(new Rect(0, 0, 1, 1).translate(1, 1).origin).toEqual(vec2.fromValues(1, 1));
  253. });
  254. it('scaleX', () => {
  255. expect(new Rect(0, 0, 1, 1).scaleX(2).size).toEqual(vec2.fromValues(2, 1));
  256. });
  257. it('scaleY', () => {
  258. expect(new Rect(0, 0, 1, 1).scaleY(2).size).toEqual(vec2.fromValues(1, 2));
  259. });
  260. it('scale', () => {
  261. expect(new Rect(0, 0, 1, 1).scale(2, 2).size).toEqual(vec2.fromValues(2, 2));
  262. });
  263. it('equals', () => {
  264. expect(new Rect(1, 0, 0, 0).equals(new Rect(0, 0, 0, 0))).toBe(false);
  265. expect(new Rect(0, 1, 0, 0).equals(new Rect(0, 0, 0, 0))).toBe(false);
  266. expect(new Rect(0, 0, 1, 0).equals(new Rect(0, 0, 0, 0))).toBe(false);
  267. expect(new Rect(0, 0, 0, 1).equals(new Rect(0, 0, 0, 0))).toBe(false);
  268. });
  269. it('scaledBy', () => {
  270. expect(new Rect(0, 0, 1, 1).scale(3, 4).equals(new Rect(0, 0, 3, 4))).toBe(true);
  271. });
  272. it('scaleOriginBy', () => {
  273. expect(new Rect(1, 1, 1, 1).scaleOriginBy(2, 2).origin).toEqual(
  274. vec2.fromValues(2, 2)
  275. );
  276. });
  277. });
  278. });
  279. describe('findRangeBinarySearch', () => {
  280. it('throws if target is out of range', () => {
  281. expect(() =>
  282. findRangeBinarySearch({low: 1, high: 2}, () => 0, 0, Number.MIN_SAFE_INTEGER)
  283. ).toThrow('Target has to be in range of low <= target <= high, got 1 <= 0 <= 2');
  284. });
  285. it('finds in single iteration', () => {
  286. const text = new Array(10)
  287. .fill(0)
  288. .map((_, i) => String.fromCharCode(i + 97))
  289. .join('');
  290. const fn = jest.fn().mockImplementation(n => {
  291. return text.substring(0, n).length;
  292. });
  293. const target = 2;
  294. const precision = 1;
  295. // First iteration will halve 1+3, next iteration will compare 2-1 <= 1 and return [1,2]
  296. const [low, high] = findRangeBinarySearch({low: 1, high: 3}, fn, target, precision);
  297. expect([low, high]).toEqual([1, 2]);
  298. expect(fn).toHaveBeenCalledTimes(1);
  299. expect(text.substring(0, low)).toBe('a');
  300. });
  301. it('finds closest range', () => {
  302. const text = new Array(10)
  303. .fill(0)
  304. .map((_, i) => String.fromCharCode(i + 97))
  305. .join('');
  306. const fn = jest.fn().mockImplementation(n => {
  307. return text.substring(0, n).length;
  308. });
  309. const target = 4;
  310. const precision = 1;
  311. const [low, high] = findRangeBinarySearch({low: 0, high: 10}, fn, target, precision);
  312. expect([low, high]).toEqual([3.75, 4.375]);
  313. expect(fn).toHaveBeenCalledTimes(4);
  314. expect(text.substring(0, low)).toBe('abc');
  315. });
  316. });
  317. describe('trimTextCenter', () => {
  318. it('trims nothing if low > length', () => {
  319. expect(trimTextCenter('abc', 4)).toBe('abc');
  320. });
  321. it('trims center perfectly', () => {
  322. expect(trimTextCenter('abcdef', 5.5)).toBe(`ab${ELLIPSIS}ef`);
  323. });
  324. it('favors prefix length', () => {
  325. expect(trimTextCenter('abcdef', 5)).toBe(`ab${ELLIPSIS}f`);
  326. });
  327. });