mock.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. import * as fs from 'fs/promises';
  2. import * as node from '../source/node.js';
  3. import * as path from 'path';
  4. import * as url from 'url';
  5. import * as worker_threads from 'worker_threads';
  6. const mock = {};
  7. mock.Context = class {
  8. constructor(host, folder, identifier, stream, entries) {
  9. this._host = host;
  10. this._folder = folder;
  11. this._identifier = identifier;
  12. this._stream = stream;
  13. this._entries = entries;
  14. }
  15. get identifier() {
  16. return this._identifier;
  17. }
  18. get stream() {
  19. return this._stream;
  20. }
  21. get entries() {
  22. return this._entries;
  23. }
  24. async request(file, encoding, base) {
  25. return this._host.request(file, encoding, base === undefined ? this._folder : base);
  26. }
  27. async require(id) {
  28. return this._host.require(id);
  29. }
  30. error(error, fatal) {
  31. this._host.exception(error, fatal);
  32. }
  33. };
  34. class CSSStyleDeclaration {
  35. constructor() {
  36. this._properties = new Map();
  37. }
  38. setProperty(name, value) {
  39. this._properties.set(name, value);
  40. }
  41. removeProperty(name) {
  42. this._properties.delete(name);
  43. }
  44. }
  45. class DOMTokenList {
  46. constructor(element) {
  47. this._element = element;
  48. }
  49. add(...tokens) {
  50. const value = this._element.getAttribute('class') || '';
  51. const set = new Set(value.split(' ').concat(...tokens));
  52. this._element.setAttribute('class', Array.from(set).filter((s) => s).join(' '));
  53. }
  54. contains(token) {
  55. const value = this._element.getAttribute('class');
  56. if (value === null || value.indexOf(token) === -1) {
  57. return false;
  58. }
  59. return value.split(' ').some((s) => s === token);
  60. }
  61. }
  62. class HTMLElement {
  63. constructor(document) {
  64. this._document = document;
  65. this._childNodes = [];
  66. this._attributes = new Map();
  67. this._style = new CSSStyleDeclaration();
  68. }
  69. get style() {
  70. return this._style;
  71. }
  72. get childNodes() {
  73. return this._childNodes;
  74. }
  75. get firstChild() {
  76. return this._childNodes.length > 0 ? this._childNodes[0] : null;
  77. }
  78. get lastChild() {
  79. const index = this._childNodes.length - 1;
  80. if (index >= 0) {
  81. return this._childNodes[index];
  82. }
  83. return null;
  84. }
  85. appendChild(node) {
  86. this._childNodes.push(node);
  87. }
  88. insertBefore(newNode, referenceNode) {
  89. const index = this._childNodes.indexOf(referenceNode);
  90. if (index !== -1) {
  91. this._childNodes.splice(index, 0, newNode);
  92. }
  93. }
  94. removeChild(node) {
  95. const index = this._childNodes.lastIndexOf(node);
  96. if (index !== -1) {
  97. this._childNodes.splice(index, 1);
  98. this._document._removeElement(node);
  99. }
  100. }
  101. replaceChildren(...nodes) {
  102. for (const child of this._childNodes) {
  103. this._document._removeElement(child);
  104. }
  105. this._childNodes = [...nodes];
  106. }
  107. replaceChild(newChild, oldChild) {
  108. const index = this._childNodes.indexOf(oldChild);
  109. if (index !== -1) {
  110. this._childNodes[index] = newChild;
  111. }
  112. return oldChild;
  113. }
  114. setAttribute(name, value) {
  115. if (name === 'id') {
  116. this._document._updateElementId(this, value);
  117. }
  118. this._attributes.set(name, value);
  119. }
  120. hasAttribute(name) {
  121. return this._attributes.has(name);
  122. }
  123. getAttribute(name) {
  124. return this._attributes.has(name) ? this._attributes.get(name) : null;
  125. }
  126. getElementsByClassName(name) {
  127. const elements = [];
  128. for (const node of this._childNodes) {
  129. if (node instanceof HTMLElement) {
  130. elements.push(...node.getElementsByClassName(name));
  131. if (node.classList.contains(name)) {
  132. elements.push(node);
  133. }
  134. }
  135. }
  136. return elements;
  137. }
  138. addEventListener(/* event, callback */) {
  139. }
  140. removeEventListener(/* event, callback */) {
  141. }
  142. get classList() {
  143. this._classList = this._classList || new DOMTokenList(this);
  144. return this._classList;
  145. }
  146. getBBox() {
  147. return { x: 0, y: 0, width: 10, height: 10 };
  148. }
  149. getBoundingClientRect() {
  150. return { left: 0, top: 0, width: 0, height: 0 };
  151. }
  152. scrollTo() {
  153. }
  154. focus() {
  155. }
  156. }
  157. class Document {
  158. constructor() {
  159. this._elements = new Map();
  160. this._documentElement = new HTMLElement(this);
  161. this._body = new HTMLElement(this);
  162. this._documentElement.appendChild(this._body);
  163. }
  164. get documentElement() {
  165. return this._documentElement;
  166. }
  167. get body() {
  168. return this._body;
  169. }
  170. createElement(name) {
  171. const element = new HTMLElement(this);
  172. element.tagName = name.toUpperCase();
  173. return element;
  174. }
  175. createElementNS(namespace, name) {
  176. const element = new HTMLElement(this);
  177. element.namespaceURI = namespace;
  178. element.tagName = name;
  179. return element;
  180. }
  181. createTextNode(text) {
  182. const element = new HTMLElement(this);
  183. element.textContent = text;
  184. return element;
  185. }
  186. addEventListener(/* event, callback */) {
  187. }
  188. removeEventListener(/* event, callback */) {
  189. }
  190. getElementById(id) {
  191. return this._elements.get(id) || null;
  192. }
  193. _removeElement(element) {
  194. this._updateElementId(element, null);
  195. for (const child of element.childNodes) {
  196. this._removeElement(child);
  197. }
  198. }
  199. _updateElementId(element, newId) {
  200. const previous = element.getAttribute('id');
  201. if (previous) {
  202. this._elements.delete(previous);
  203. }
  204. if (newId) {
  205. this._elements.set(newId, element);
  206. }
  207. }
  208. }
  209. class Window {
  210. constructor() {
  211. this._document = new Document();
  212. }
  213. get document() {
  214. return this._document;
  215. }
  216. addEventListener(/* event, callback */) {
  217. }
  218. removeEventListener(/* event, callback */) {
  219. }
  220. requestAnimationFrame(callback) {
  221. callback();
  222. }
  223. }
  224. mock.Host = class {
  225. constructor(environment) {
  226. this._environment = environment;
  227. this._errors = [];
  228. mock.Host.source = mock.Host.source || this._dirname('..', 'source');
  229. if (!mock.Host.window) {
  230. mock.Host.window = new Window();
  231. const document = mock.Host.window.document;
  232. for (const id of ['target', 'sidebar']) {
  233. const element = document.createElement('div');
  234. element.setAttribute('id', id);
  235. document.body.appendChild(element);
  236. }
  237. }
  238. }
  239. async view(/* view */) {
  240. }
  241. async start() {
  242. }
  243. get window() {
  244. return mock.Host.window;
  245. }
  246. get document() {
  247. return this.window.document;
  248. }
  249. get errors() {
  250. return this._errors;
  251. }
  252. get type() {
  253. return 'Test';
  254. }
  255. environment(name) {
  256. return this._environment[name];
  257. }
  258. update() {
  259. }
  260. screen(/* name */) {
  261. }
  262. async require(id) {
  263. const file = path.join(mock.Host.source, `${id}.js`);
  264. return await import(`file://${file}`);
  265. }
  266. worker(id) {
  267. const file = path.join(mock.Host.source, `${id}.js`);
  268. const worker = new worker_threads.Worker(file);
  269. worker.addEventListener = (type, listener) => {
  270. worker.on(type, (message) => listener({ data: message }));
  271. };
  272. return worker;
  273. }
  274. async request(file, encoding, basename) {
  275. const pathname = path.join(basename || mock.Host.source, file);
  276. const exists = await this._access(pathname);
  277. if (!exists) {
  278. throw new Error(`The file '${file}' does not exist.`);
  279. }
  280. const stats = await fs.stat(pathname);
  281. if (stats.isDirectory()) {
  282. throw new Error(`The path '${file}' is a directory.`);
  283. }
  284. if (encoding) {
  285. return await fs.readFile(pathname, encoding);
  286. }
  287. return new node.FileStream(pathname, 0, stats.size, stats.mtimeMs);
  288. }
  289. event(/* name, params */) {
  290. }
  291. exception(error /*, fatal */) {
  292. this._errors.push(error);
  293. }
  294. message() {
  295. }
  296. async _access(path) {
  297. try {
  298. await fs.access(path);
  299. return true;
  300. } catch {
  301. return false;
  302. }
  303. }
  304. _dirname(...args) {
  305. const file = url.fileURLToPath(import.meta.url);
  306. const dir = path.dirname(file);
  307. return path.join(dir, ...args);
  308. }
  309. };
  310. export const Host = mock.Host;
  311. export const Context = mock.Context;