8
0

schema2.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import Schema from '../../../src/model/schema2';
  6. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  7. import Element from '../../../src/model/element';
  8. describe( 'Schema', () => {
  9. let schema, root1, r1p1, r1p2, r1bQ, r1bQp, root2;
  10. beforeEach( () => {
  11. schema = new Schema();
  12. root1 = new Element( '$root', null, [
  13. new Element( 'paragraph', null, 'foo' ),
  14. new Element( 'paragraph', { align: 'right' }, 'bar' ),
  15. new Element( 'blockQuote', null, [
  16. new Element( 'paragraph', null, 'foo' )
  17. ] )
  18. ] );
  19. r1p1 = root1.getChild( 0 );
  20. r1p2 = root1.getChild( 1 );
  21. r1bQ = root1.getChild( 2 );
  22. r1bQp = r1bQ.getChild( 0 );
  23. root2 = new Element( '$root2' );
  24. } );
  25. describe( 'register()', () => {
  26. it( 'allows registering an item', () => {
  27. schema.register( 'foo' );
  28. expect( schema.getRule( 'foo' ) ).to.be.an( 'object' );
  29. } );
  30. it( 'copies rules', () => {
  31. const rules = {};
  32. schema.register( 'foo', rules );
  33. rules.isBlock = true;
  34. expect( schema.getRules().foo ).to.not.have.property( 'isBlock' );
  35. } );
  36. it( 'throws when trying to register for a single item twice', () => {
  37. schema.register( 'foo' );
  38. expect( () => {
  39. schema.register( 'foo' );
  40. } ).to.throw( CKEditorError, /^schema-cannot-register-item-twice:/ );
  41. } );
  42. } );
  43. describe( 'extend()', () => {
  44. it( 'allows extending item\'s rules', () => {
  45. schema.register( 'foo' );
  46. schema.extend( 'foo', {
  47. isBlock: true
  48. } );
  49. expect( schema.getRule( 'foo' ) ).to.have.property( 'isBlock', true );
  50. } );
  51. it( 'copies rules', () => {
  52. schema.register( 'foo', {} );
  53. const rules = {};
  54. schema.extend( 'foo', rules );
  55. rules.isBlock = true;
  56. expect( schema.getRules().foo ).to.not.have.property( 'isBlock' );
  57. } );
  58. it( 'throws when trying to extend a not yet registered item', () => {
  59. expect( () => {
  60. schema.extend( 'foo' );
  61. } ).to.throw( CKEditorError, /^schema-cannot-extend-missing-item:/ );
  62. } );
  63. } );
  64. describe( 'getRules()', () => {
  65. it( 'returns compiled rules', () => {
  66. schema.register( '$root' );
  67. schema.register( 'foo', {
  68. allowIn: '$root'
  69. } );
  70. schema.extend( 'foo', {
  71. isBlock: true
  72. } );
  73. const rules = schema.getRules();
  74. expect( rules.foo ).to.deep.equal( {
  75. name: 'foo',
  76. allowIn: [ '$root' ],
  77. allowAttributes: [],
  78. isBlock: true
  79. } );
  80. } );
  81. it( 'copies all is* types', () => {
  82. schema.register( 'foo', {
  83. isBlock: true,
  84. isFoo: true
  85. } );
  86. schema.extend( 'foo', {
  87. isBar: true,
  88. isFoo: false // Just to check that the last one wins.
  89. } );
  90. const rules = schema.getRules();
  91. expect( rules.foo ).to.have.property( 'isBlock', true );
  92. expect( rules.foo ).to.have.property( 'isFoo', false );
  93. expect( rules.foo ).to.have.property( 'isBar', true );
  94. } );
  95. it( 'does not recompile rules if not needed', () => {
  96. schema.register( 'foo' );
  97. expect( schema.getRules() ).to.equal( schema.getRules() );
  98. } );
  99. it( 'ensures no duplicates in allowIn', () => {
  100. schema.register( '$root' );
  101. schema.register( 'foo', {
  102. allowIn: '$root'
  103. } );
  104. schema.extend( 'foo', {
  105. allowIn: '$root'
  106. } );
  107. const rules = schema.getRules();
  108. expect( rules.foo ).to.deep.equal( {
  109. name: 'foo',
  110. allowIn: [ '$root' ],
  111. allowAttributes: []
  112. } );
  113. } );
  114. it( 'ensures no unregistered items in allowIn', () => {
  115. schema.register( 'foo', {
  116. allowIn: '$root'
  117. } );
  118. const rules = schema.getRules();
  119. expect( rules.foo ).to.deep.equal( {
  120. name: 'foo',
  121. allowIn: [],
  122. allowAttributes: []
  123. } );
  124. } );
  125. it( 'ensures no duplicates in allowAttributes', () => {
  126. schema.register( 'paragraph', {
  127. allowAttributes: 'foo'
  128. } );
  129. schema.extend( 'paragraph', {
  130. allowAttributes: 'foo'
  131. } );
  132. const rules = schema.getRules();
  133. expect( rules.paragraph ).to.deep.equal( {
  134. name: 'paragraph',
  135. allowIn: [],
  136. allowAttributes: [ 'foo' ]
  137. } );
  138. } );
  139. it( 'ensures no duplicates in allowAttributes duplicated by allowAttributesOf', () => {
  140. schema.register( 'paragraph', {
  141. allowAttributes: 'foo',
  142. allowAttributesOf: '$block'
  143. } );
  144. schema.register( '$block', {
  145. allowAttributes: 'foo'
  146. } );
  147. const rules = schema.getRules();
  148. expect( rules.paragraph ).to.deep.equal( {
  149. name: 'paragraph',
  150. allowIn: [],
  151. allowAttributes: [ 'foo' ]
  152. } );
  153. } );
  154. } );
  155. describe( 'getRule()', () => {
  156. // TODO
  157. } );
  158. describe( 'isRegistered()', () => {
  159. it( 'returns true if an item was registered', () => {
  160. schema.register( 'foo' );
  161. expect( schema.isRegistered( 'foo' ) ).to.be.true;
  162. } );
  163. it( 'returns false if an item was not registered', () => {
  164. expect( schema.isRegistered( 'foo' ) ).to.be.false;
  165. } );
  166. } );
  167. describe( 'isBlock()', () => {
  168. it( 'returns true if an item was registered as a block', () => {
  169. schema.register( 'foo', {
  170. isBlock: true
  171. } );
  172. expect( schema.isBlock( 'foo' ) ).to.be.true;
  173. } );
  174. it( 'returns false if an item was not registered as a block', () => {
  175. schema.register( 'foo' );
  176. expect( schema.isBlock( 'foo' ) ).to.be.false;
  177. } );
  178. it( 'returns false if an item was not registered at all', () => {
  179. expect( schema.isBlock( 'foo' ) ).to.be.false;
  180. } );
  181. } );
  182. describe( 'isLimit()', () => {
  183. it( 'returns true if an item was registered as a limit element', () => {
  184. schema.register( 'foo', {
  185. isLimit: true
  186. } );
  187. expect( schema.isLimit( 'foo' ) ).to.be.true;
  188. } );
  189. it( 'returns false if an item was not registered as a limit element', () => {
  190. schema.register( 'foo' );
  191. expect( schema.isLimit( 'foo' ) ).to.be.false;
  192. } );
  193. it( 'returns false if an item was not registered at all', () => {
  194. expect( schema.isLimit( 'foo' ) ).to.be.false;
  195. } );
  196. } );
  197. describe( 'isObject()', () => {
  198. it( 'returns true if an item was registered as an object', () => {
  199. schema.register( 'foo', {
  200. isObject: true
  201. } );
  202. expect( schema.isObject( 'foo' ) ).to.be.true;
  203. } );
  204. it( 'returns false if an item was not registered as an object', () => {
  205. schema.register( 'foo' );
  206. expect( schema.isObject( 'foo' ) ).to.be.false;
  207. } );
  208. it( 'returns false if an item was not registered at all', () => {
  209. expect( schema.isObject( 'foo' ) ).to.be.false;
  210. } );
  211. } );
  212. describe( 'checkChild()', () => {
  213. describe( 'allowIn cases', () => {
  214. it( 'passes $root>paragraph', () => {
  215. schema.register( '$root' );
  216. schema.register( 'paragraph', {
  217. allowIn: '$root'
  218. } );
  219. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  220. } );
  221. it( 'passes $root>paragraph and $root2>paragraph – support for array values', () => {
  222. schema.register( '$root' );
  223. schema.register( '$root2' );
  224. schema.register( 'paragraph', {
  225. allowIn: [ '$root', '$root2' ]
  226. } );
  227. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  228. expect( schema.checkChild( root2, r1p1 ) ).to.be.true;
  229. } );
  230. it( 'passes $root>paragraph[align] – attributes does not matter', () => {
  231. schema.register( '$root' );
  232. schema.register( 'paragraph', {
  233. allowIn: '$root'
  234. } );
  235. expect( schema.checkChild( root1, r1p2 ) ).to.be.true;
  236. } );
  237. it( 'passes $root>div>div – in case of circular refs', () => {
  238. schema.register( '$root' );
  239. schema.register( 'div', {
  240. allowIn: [ '$root', 'div' ]
  241. } );
  242. const div = new Element( 'div' );
  243. root1.appendChildren( div );
  244. const div2 = new Element( 'div' );
  245. expect( schema.checkChild( div, div2 ) ).to.be.true;
  246. } );
  247. it( 'passes $root>div>div – in case of circular refs, when div1==div2', () => {
  248. schema.register( '$root' );
  249. schema.register( 'div', {
  250. allowIn: [ '$root', 'div' ]
  251. } );
  252. const div = new Element( 'div' );
  253. root1.appendChildren( div );
  254. expect( schema.checkChild( div, div ) ).to.be.true;
  255. } );
  256. it( 'rejects $root>paragraph – unregistered paragraph', () => {
  257. schema.register( '$root' );
  258. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  259. } );
  260. it( 'rejects $root>paragraph – registered different item', () => {
  261. schema.register( '$root' );
  262. schema.register( 'paragraph' );
  263. schema.register( 'listItem', {
  264. allowIn: '$root'
  265. } );
  266. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  267. } );
  268. it( 'rejects $root>paragraph – paragraph allowed in different context', () => {
  269. schema.register( '$root' );
  270. schema.register( '$fancyRoot' );
  271. schema.register( 'paragraph', {
  272. allowIn: '$fancyRoot'
  273. } );
  274. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  275. } );
  276. it( 'rejects $root>blockQuote>paragraph – since paragraph is only allowed in $root', () => {
  277. schema.register( '$root' );
  278. schema.register( 'paragraph', {
  279. allowIn: '$root'
  280. } );
  281. expect( schema.checkChild( r1bQ, r1bQp ) ).to.be.false;
  282. } );
  283. it( 'rejects $root>blockQuote>paragraph – since paragraph is only allowed in $root v2', () => {
  284. schema.register( '$root' );
  285. schema.register( 'blockQuote', {
  286. allowIn: '$root'
  287. } );
  288. schema.register( 'paragraph', {
  289. allowIn: '$root'
  290. } );
  291. expect( schema.checkChild( r1bQ, r1bQp ) ).to.be.false;
  292. } );
  293. it( 'rejects $root>blockQuote>paragraph>$text - since paragraph is not allowed in blockQuote', () => {
  294. schema.register( '$root' );
  295. schema.register( 'paragraph', {
  296. allowIn: '$root'
  297. } );
  298. schema.register( '$text', {
  299. allowIn: 'paragraph'
  300. } );
  301. expect( schema.checkChild( root1, r1bQp.getChild( 0 ) ) ).to.be.false;
  302. } );
  303. it( 'rejects $root>blockQuote>paragraph>$text - since blockQuote is not allowed in $root', () => {
  304. schema.register( '$root' );
  305. schema.register( 'blockQuote' );
  306. schema.register( 'paragraph', {
  307. allowIn: [ 'blockQuote', '$root' ]
  308. } );
  309. schema.register( '$text', {
  310. allowIn: 'paragraph'
  311. } );
  312. expect( schema.checkChild( root1, r1bQp.getChild( 0 ) ) ).to.be.false;
  313. } );
  314. } );
  315. describe( 'allowWhere cases', () => {
  316. it( 'passes $root>paragraph – paragraph inherits from $block', () => {
  317. schema.register( '$root' );
  318. schema.register( '$block', {
  319. allowIn: '$root'
  320. } );
  321. schema.register( 'paragraph', {
  322. allowWhere: '$block'
  323. } );
  324. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  325. } );
  326. it( 'supports the array syntax', () => {
  327. schema.register( '$root' );
  328. schema.register( '$root2' );
  329. schema.register( '$block', {
  330. allowIn: '$root'
  331. } );
  332. schema.register( '$block2', {
  333. allowIn: '$root2'
  334. } );
  335. schema.register( 'paragraph', {
  336. allowWhere: [ '$block', '$block2' ]
  337. } );
  338. expect( schema.checkChild( root1, r1p1 ), '$root' ).to.be.true;
  339. expect( schema.checkChild( root2, r1p1 ), '$root2' ).to.be.true;
  340. } );
  341. // This checks if some inapropriate caching or preprocessing isn't applied by register().
  342. it( 'passes $root>paragraph – paragraph inherits from $block, order of rules does not matter', () => {
  343. schema.register( '$root' );
  344. schema.register( 'paragraph', {
  345. allowWhere: '$block'
  346. } );
  347. schema.register( '$block', {
  348. allowIn: '$root'
  349. } );
  350. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  351. } );
  352. it( 'passes $root>paragraph – paragraph inherits from $specialBlock which inherits from $block', () => {
  353. schema.register( '$root' );
  354. schema.register( '$block', {
  355. allowIn: '$root'
  356. } );
  357. schema.register( '$specialBlock', {
  358. allowWhere: '$block'
  359. } );
  360. schema.register( 'paragraph', {
  361. allowWhere: '$specialBlock'
  362. } );
  363. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  364. } );
  365. it( 'rejects $root>paragraph – paragraph inherits from $block but $block is not allowed in $root', () => {
  366. schema.register( '$root' );
  367. schema.register( '$block' );
  368. schema.register( 'paragraph', {
  369. allowWhere: '$block'
  370. } );
  371. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  372. } );
  373. it( 'rejects $root>paragraph>$text – paragraph inherits from $block but $block is not allowed in $root', () => {
  374. schema.register( '$root' );
  375. schema.register( '$block' );
  376. schema.register( 'paragraph', {
  377. allowWhere: '$block'
  378. } );
  379. schema.register( '$text', {
  380. allowIn: 'paragraph'
  381. } );
  382. expect( schema.checkChild( root1, r1p1.getChild( 0 ) ) ).to.be.false;
  383. } );
  384. } );
  385. describe( 'allowContentOf cases', () => {
  386. it( 'passes $root2>paragraph – $root2 inherits from $root', () => {
  387. schema.register( '$root' );
  388. schema.register( 'paragraph', {
  389. allowIn: '$root'
  390. } );
  391. schema.register( '$root2', {
  392. allowContentOf: '$root'
  393. } );
  394. expect( schema.checkChild( root2, r1p1 ) ).to.be.true;
  395. } );
  396. it( 'supports the array syntax', () => {
  397. schema.register( '$root' );
  398. schema.register( '$root2' );
  399. schema.register( 'paragraph', {
  400. allowIn: '$root'
  401. } );
  402. schema.register( 'heading1', {
  403. allowIn: '$root2'
  404. } );
  405. schema.register( '$root3', {
  406. allowContentOf: [ '$root', '$root2' ]
  407. } );
  408. const root3 = new Element( '$root3' );
  409. const heading1 = new Element( 'heading1' );
  410. expect( schema.checkChild( root3, r1p1 ), 'paragraph' ).to.be.true;
  411. expect( schema.checkChild( root3, heading1 ), 'heading1' ).to.be.true;
  412. } );
  413. it( 'passes $root2>paragraph – $root2 inherits from $root, order of rules does not matter', () => {
  414. schema.register( '$root' );
  415. schema.register( '$root2', {
  416. allowContentOf: '$root'
  417. } );
  418. schema.register( 'paragraph', {
  419. allowIn: '$root'
  420. } );
  421. expect( schema.checkChild( root2, r1p1 ) ).to.be.true;
  422. } );
  423. it( 'passes $root>paragraph>$text – paragraph inherits content of $block', () => {
  424. schema.register( '$root' );
  425. schema.register( '$block' );
  426. schema.register( 'paragraph', {
  427. allowIn: '$root',
  428. allowContentOf: '$block'
  429. } );
  430. schema.register( '$text', {
  431. allowIn: '$block'
  432. } );
  433. expect( schema.checkChild( r1p1, r1p1.getChild( 0 ) ) ).to.be.true;
  434. } );
  435. // TODO we need to make it possible to disallow bQ in bQ.
  436. it( 'passes $root>blockQuote>paragraph – blockQuote inherits content of $root', () => {
  437. schema.register( '$root' );
  438. schema.register( 'blockQuote', {
  439. allowIn: '$root',
  440. allowContentOf: '$root'
  441. } );
  442. schema.register( 'paragraph', {
  443. allowIn: '$root'
  444. } );
  445. expect( schema.checkChild( r1bQ, r1bQp ) ).to.be.true;
  446. } );
  447. it( 'rejects $root2>paragraph – $root2 inherits from $root, but paragraph is not allowed there anyway', () => {
  448. schema.register( '$root' );
  449. schema.register( 'paragraph' );
  450. schema.register( '$root2', {
  451. allowContentOf: '$root'
  452. } );
  453. expect( schema.checkChild( root2, r1p1 ) ).to.be.false;
  454. } );
  455. } );
  456. describe( 'mix of allowContentOf and allowWhere', () => {
  457. it( 'passes $root>paragraph>$text – paragraph inherits all from $block', () => {
  458. schema.register( '$root' );
  459. schema.register( '$block', {
  460. allowIn: '$root'
  461. } );
  462. schema.register( 'paragraph', {
  463. allowContentOf: '$block',
  464. allowWhere: '$block'
  465. } );
  466. schema.register( '$text', {
  467. allowIn: '$block'
  468. } );
  469. expect( schema.checkChild( r1p1, r1p1.getChild( 0 ) ) ).to.be.true;
  470. } );
  471. it( 'passes $root>paragraph and $root2>paragraph – where $root2 inherits content of $root' +
  472. 'and paragraph inherits allowWhere from $block', () => {
  473. schema.register( '$root' );
  474. schema.register( '$root2', {
  475. allowContentOf: '$root'
  476. } );
  477. schema.register( '$block', {
  478. allowIn: '$root'
  479. } );
  480. schema.register( 'paragraph', {
  481. allowWhere: '$block'
  482. } );
  483. expect( schema.checkChild( root1, 'paragraph' ), 'root1' ).to.be.true;
  484. expect( schema.checkChild( root2, 'paragraph' ), 'root2' ).to.be.true;
  485. } );
  486. it( 'passes d>a where d inherits content of c which inherits content of b', () => {
  487. schema.register( 'b' );
  488. schema.register( 'a', { allowIn: 'b' } );
  489. schema.register( 'c', { allowContentOf: 'b' } );
  490. schema.register( 'd', { allowContentOf: 'c' } );
  491. const d = new Element( 'd' );
  492. expect( schema.checkChild( d, 'a' ) ).to.be.true;
  493. } );
  494. // This case won't pass becuase we compile the rules in a pretty naive way.
  495. // To make chains like this work we'd need to repeat compilation of allowContentOf rules
  496. // as long as the previous iteration found something to compile.
  497. // This way, even though we'd not compile d<-c in the first run, we'd still find b<-c
  498. // and since we've found something, we'd now try d<-c which would work.
  499. //
  500. // We ignore those situations for now as they are very unlikely to happen and would
  501. // significantly raised the complexity of rule compilation.
  502. //
  503. // it( 'passes d>a where d inherits content of c which inherits content of b', () => {
  504. // schema.register( 'b' );
  505. // schema.register( 'a', { allowIn: 'b' } );
  506. // schema.register( 'd', { allowContentOf: 'c' } );
  507. // schema.register( 'c', { allowContentOf: 'b' } );
  508. //
  509. // const d = new Element( 'd' );
  510. //
  511. // expect( schema.checkChild( d, 'a' ) ).to.be.true;
  512. // } );
  513. } );
  514. // We need to handle cases where some independent features registered rules which might use
  515. // optional elements (elements which might not have been registered).
  516. describe( 'missing rules', () => {
  517. it( 'does not break when trying to check a child which is not registered', () => {
  518. schema.register( '$root' );
  519. expect( schema.checkChild( root1, 'foo404' ) ).to.be.false;
  520. } );
  521. it( 'does not break when trying to check registered child in a context which contains unregistered elements', () => {
  522. const foo404 = new Element( 'foo404' );
  523. root1.appendChildren( foo404 );
  524. schema.register( '$root' );
  525. schema.register( '$text', {
  526. allowIn: '$root'
  527. } );
  528. expect( schema.checkChild( foo404, '$text' ) ).to.be.false;
  529. } );
  530. it( 'does not break when used allowedIn pointing to an unregistered element', () => {
  531. schema.register( '$root' );
  532. schema.register( '$text', {
  533. allowIn: 'foo404'
  534. } );
  535. expect( schema.checkChild( root1, '$text' ) ).to.be.false;
  536. } );
  537. it( 'does not break when used allowWhere pointing to an unregistered element', () => {
  538. schema.register( '$root' );
  539. schema.register( '$text', {
  540. allowWhere: 'foo404'
  541. } );
  542. expect( schema.checkChild( root1, '$text' ) ).to.be.false;
  543. } );
  544. it( 'does not break when used allowContentOf pointing to an unregistered element', () => {
  545. schema.register( '$root', {
  546. allowContentOf: 'foo404'
  547. } );
  548. schema.register( '$text', {
  549. allowIn: '$root'
  550. } );
  551. expect( schema.checkChild( root1, '$text' ) ).to.be.true;
  552. } );
  553. it( 'checks whether allowIn uses a registered element', () => {
  554. schema.register( 'paragraph', {
  555. allowIn: '$root'
  556. } );
  557. // $root isn't registered!
  558. expect( schema.checkChild( root1, 'paragraph' ) ).to.be.false;
  559. } );
  560. } );
  561. } );
  562. describe( 'checkAttribute()', () => {
  563. describe( 'allowAttributes', () => {
  564. it( 'passes paragraph[align]', () => {
  565. schema.register( 'paragraph', {
  566. allowAttributes: 'align'
  567. } );
  568. expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.true;
  569. } );
  570. it( 'passes paragraph[align] and paragraph[dir] – support for array values', () => {
  571. schema.register( 'paragraph', {
  572. allowAttributes: [ 'align', 'dir' ]
  573. } );
  574. expect( schema.checkAttribute( r1p1, 'align' ), 'align' ).to.be.true;
  575. expect( schema.checkAttribute( r1p1, 'dir' ), 'dir' ).to.be.true;
  576. } );
  577. it( 'passes paragraph>$text[bold]', () => {
  578. schema.register( 'paragraph' );
  579. schema.register( '$text', {
  580. allowIn: 'paragraph',
  581. allowAttributes: 'bold'
  582. } );
  583. expect( schema.checkAttribute( r1p1.getChild( 0 ), 'bold' ) ).to.be.true;
  584. } );
  585. } );
  586. describe( 'allowAttributesOf', () => {
  587. it( 'passes paragraph[align] – paragraph inherits from $block', () => {
  588. schema.register( '$block', {
  589. allowAttributes: 'align'
  590. } );
  591. schema.register( 'paragraph', {
  592. allowAttributesOf: '$block'
  593. } );
  594. expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.true;
  595. } );
  596. it( 'passes paragraph[align] and paragraph[dir] – support for array values', () => {
  597. schema.register( '$block', {
  598. allowAttributes: 'align'
  599. } );
  600. schema.register( '$block2', {
  601. allowAttributes: 'dir'
  602. } );
  603. schema.register( 'paragraph', {
  604. allowAttributesOf: [ '$block', '$block2' ]
  605. } );
  606. expect( schema.checkAttribute( r1p1, 'align' ), 'align' ).to.be.true;
  607. expect( schema.checkAttribute( r1p1, 'dir' ), 'dir' ).to.be.true;
  608. } );
  609. it( 'passes paragraph[align] and paragraph[dir] – support for combined allowAttributes and allowAttributesOf', () => {
  610. schema.register( '$block', {
  611. allowAttributes: 'align'
  612. } );
  613. schema.register( 'paragraph', {
  614. allowAttributes: 'dir',
  615. allowAttributesOf: '$block'
  616. } );
  617. expect( schema.checkAttribute( r1p1, 'align' ), 'align' ).to.be.true;
  618. expect( schema.checkAttribute( r1p1, 'dir' ), 'dir' ).to.be.true;
  619. } );
  620. // The support for allowAttributesOf is broken in the similar way as for allowContentOf (see the comment above).
  621. // However, those situations are rather theoretical, so we're not going to waste time on them now.
  622. } );
  623. describe( 'missing rules', () => {
  624. it( 'does not crash when checking an attribute of a unregistered element', () => {
  625. expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.false;
  626. } );
  627. it( 'does not crash when checking when inheriting attributes of a unregistered element', () => {
  628. schema.register( 'paragraph', {
  629. allowAttributesOf: '$block'
  630. } );
  631. expect( schema.checkAttribute( r1p1, 'whatever' ) ).to.be.false;
  632. } );
  633. } );
  634. } );
  635. describe( 'real scenarios', () => {
  636. let r1bQi, r1i, r1lI, r1h, r1bQlI;
  637. const rules = [
  638. () => {
  639. schema.register( 'paragraph', {
  640. allowWhere: '$block',
  641. allowContentOf: '$block',
  642. allowAttributesOf: '$block'
  643. } );
  644. },
  645. () => {
  646. schema.register( 'heading1', {
  647. allowWhere: '$block',
  648. allowContentOf: '$block',
  649. allowAttributesOf: '$block'
  650. } );
  651. },
  652. () => {
  653. schema.register( 'listItem', {
  654. allowWhere: '$block',
  655. allowContentOf: '$block',
  656. allowAttributes: [ 'indent', 'type' ],
  657. allowAttributesOf: '$block'
  658. } );
  659. },
  660. () => {
  661. schema.register( 'blockQuote', {
  662. allowWhere: '$block',
  663. allowContentOf: '$root'
  664. } );
  665. // Disallow blockQuote in blockQuote.
  666. schema.on( 'checkChild', ( evt, args ) => {
  667. const context = args[ 0 ];
  668. const child = args[ 1 ];
  669. const childRule = schema.getRule( child );
  670. if ( childRule.name == 'blockQuote' && context[ context.length - 1 ].name == 'blockQuote' ) {
  671. evt.stop();
  672. evt.return = false;
  673. }
  674. }, { priority: 'high' } );
  675. },
  676. () => {
  677. schema.register( 'image', {
  678. allowWhere: '$block',
  679. allowAttributes: [ 'src', 'alt' ]
  680. } );
  681. },
  682. () => {
  683. schema.register( 'caption', {
  684. allowIn: 'image',
  685. allowContentOf: '$block'
  686. } );
  687. },
  688. () => {
  689. schema.extend( '$text', {
  690. allowAttributes: [ 'bold', 'italic' ]
  691. } );
  692. // Disallow bold in heading1.
  693. schema.on( 'checkAttribute', ( evt, args ) => {
  694. const context = args[ 0 ];
  695. const contextLast = context[ context.length - 1 ];
  696. const contextSecondToLast = context[ context.length - 2 ];
  697. const attributeName = args[ 1 ];
  698. if ( contextLast.name == '$text' && contextSecondToLast.name == 'heading1' && attributeName == 'bold' ) {
  699. evt.stop();
  700. evt.return = false;
  701. }
  702. }, { priority: 'high' } );
  703. },
  704. () => {
  705. schema.extend( '$block', {
  706. allowAttributes: 'alignment'
  707. } );
  708. }
  709. ];
  710. beforeEach( () => {
  711. schema.register( '$root' );
  712. schema.register( '$block', {
  713. allowIn: '$root'
  714. } );
  715. schema.register( '$text', {
  716. allowIn: '$block'
  717. } );
  718. for ( const rule of rules ) {
  719. rule();
  720. }
  721. // or...
  722. //
  723. // Use the below code to shuffle the rules.
  724. // Don't look here, Szymon!
  725. //
  726. // const rulesCopy = rules.slice();
  727. //
  728. // while ( rulesCopy.length ) {
  729. // const r = Math.floor( Math.random() * rulesCopy.length );
  730. // rulesCopy.splice( r, 1 )[ 0 ]();
  731. // }
  732. root1 = new Element( '$root', null, [
  733. new Element( 'paragraph', null, 'foo' ),
  734. new Element( 'paragraph', { alignment: 'right' }, 'bar' ),
  735. new Element( 'listItem', { type: 'x', indent: 0 }, 'foo' ),
  736. new Element( 'heading1', null, 'foo' ),
  737. new Element( 'blockQuote', null, [
  738. new Element( 'paragraph', null, 'foo' ),
  739. new Element( 'listItem', { type: 'x', indent: 0 }, 'foo' ),
  740. new Element( 'image', null, [
  741. new Element( 'caption', null, 'foo' )
  742. ] )
  743. ] ),
  744. new Element( 'image', null, [
  745. new Element( 'caption', null, 'foo' )
  746. ] )
  747. ] );
  748. r1p1 = root1.getChild( 0 );
  749. r1p2 = root1.getChild( 1 );
  750. r1lI = root1.getChild( 2 );
  751. r1h = root1.getChild( 3 );
  752. r1bQ = root1.getChild( 4 );
  753. r1i = root1.getChild( 5 );
  754. r1bQp = r1bQ.getChild( 0 );
  755. r1bQlI = r1bQ.getChild( 1 );
  756. r1bQi = r1bQ.getChild( 2 );
  757. } );
  758. it( 'passes $root>paragraph', () => {
  759. expect( schema.checkChild( root1, 'paragraph' ) ).to.be.true;
  760. } );
  761. it( 'passes $root>paragraph>$text', () => {
  762. expect( schema.checkChild( r1p1, '$text' ), 'paragraph' ).to.be.true;
  763. expect( schema.checkChild( r1p2, '$text' ), 'paragraph[alignment]' ).to.be.true;
  764. } );
  765. it( 'passes $root>listItem', () => {
  766. expect( schema.checkChild( root1, 'listItem' ) ).to.be.true;
  767. } );
  768. it( 'passes $root>listItem>$text', () => {
  769. expect( schema.checkChild( r1lI, '$text' ) ).to.be.true;
  770. } );
  771. it( 'passes $root>blockQuote>paragraph', () => {
  772. expect( schema.checkChild( r1bQ, 'paragraph' ) ).to.be.true;
  773. } );
  774. it( 'passes $root>blockQuote>paragraph>$text', () => {
  775. expect( schema.checkChild( r1bQp, '$text' ) ).to.be.true;
  776. } );
  777. it( 'passes $root>blockQuote>listItem', () => {
  778. expect( schema.checkChild( r1bQ, 'listItem' ) ).to.be.true;
  779. } );
  780. it( 'passes $root>blockQuote>listItem>$text', () => {
  781. expect( schema.checkChild( r1bQlI, '$text' ) ).to.be.true;
  782. } );
  783. it( 'passes $root>blockQuote>image', () => {
  784. expect( schema.checkChild( r1bQ, 'image' ) ).to.be.true;
  785. } );
  786. it( 'passes $root>blockQuote>image>caption', () => {
  787. expect( schema.checkChild( r1bQi, 'caption' ) ).to.be.true;
  788. } );
  789. it( 'passes $root>blockQuote>image>caption>$text', () => {
  790. expect( schema.checkChild( r1bQi.getChild( 0 ), '$text' ) ).to.be.true;
  791. } );
  792. it( 'passes $root>image', () => {
  793. expect( schema.checkChild( root1, 'image' ) ).to.be.true;
  794. } );
  795. it( 'passes $root>image>caption', () => {
  796. expect( schema.checkChild( r1i, 'caption' ) ).to.be.true;
  797. } );
  798. it( 'passes $root>image>caption>$text', () => {
  799. expect( schema.checkChild( r1i.getChild( 0 ), '$text' ) ).to.be.true;
  800. } );
  801. it( 'rejects $root>$root', () => {
  802. expect( schema.checkChild( root1, '$root' ) ).to.be.false;
  803. } );
  804. it( 'rejects $root>$text', () => {
  805. expect( schema.checkChild( root1, '$text' ) ).to.be.false;
  806. } );
  807. it( 'rejects $root>caption', () => {
  808. expect( schema.checkChild( root1, 'caption' ) ).to.be.false;
  809. } );
  810. it( 'rejects $root>paragraph>paragraph', () => {
  811. expect( schema.checkChild( r1p1, 'paragraph' ) ).to.be.false;
  812. } );
  813. it( 'rejects $root>paragraph>paragraph>$text', () => {
  814. // Edge case because p>p should not exist in the first place.
  815. // But it's good to know that it blocks also this.
  816. const p = new Element( 'p' );
  817. r1p1.appendChildren( p );
  818. expect( schema.checkChild( p, '$text' ) ).to.be.false;
  819. } );
  820. it( 'rejects $root>paragraph>$block', () => {
  821. expect( schema.checkChild( r1p1, '$block' ) ).to.be.false;
  822. } );
  823. it( 'rejects $root>paragraph>blockQuote', () => {
  824. expect( schema.checkChild( r1p1, 'blockQuote' ) ).to.be.false;
  825. } );
  826. it( 'rejects $root>paragraph>image', () => {
  827. expect( schema.checkChild( r1p1, 'image' ) ).to.be.false;
  828. } );
  829. it( 'rejects $root>paragraph>caption', () => {
  830. expect( schema.checkChild( r1p1, 'caption' ) ).to.be.false;
  831. } );
  832. it( 'rejects $root>blockQuote>blockQuote', () => {
  833. expect( schema.checkChild( r1bQ, 'blockQuote' ) ).to.be.false;
  834. } );
  835. it( 'rejects $root>blockQuote>caption', () => {
  836. expect( schema.checkChild( r1p1, 'image' ) ).to.be.false;
  837. } );
  838. it( 'rejects $root>blockQuote>$text', () => {
  839. expect( schema.checkChild( r1bQ, '$text' ) ).to.be.false;
  840. } );
  841. it( 'rejects $root>image>$text', () => {
  842. expect( schema.checkChild( r1i, '$text' ) ).to.be.false;
  843. } );
  844. it( 'rejects $root>image>paragraph', () => {
  845. expect( schema.checkChild( r1i, 'paragraph' ) ).to.be.false;
  846. } );
  847. it( 'rejects $root>image>caption>paragraph', () => {
  848. expect( schema.checkChild( r1i.getChild( 0 ), 'paragraph' ) ).to.be.false;
  849. } );
  850. it( 'rejects $root>image>caption>blockQuote', () => {
  851. expect( schema.checkChild( r1i.getChild( 0 ), 'blockQuote' ) ).to.be.false;
  852. } );
  853. it( 'accepts attribute $root>paragraph[alignment]', () => {
  854. expect( schema.checkAttribute( r1p1, 'alignment' ) ).to.be.true;
  855. } );
  856. it( 'accepts attribute $root>paragraph>$text[bold]', () => {
  857. expect( schema.checkAttribute( r1p1.getChild( 0 ), 'bold' ) ).to.be.true;
  858. } );
  859. it( 'accepts attribute $root>heading1>$text[italic]', () => {
  860. expect( schema.checkAttribute( r1h.getChild( 0 ), 'italic' ) ).to.be.true;
  861. } );
  862. it( 'accepts attribute $root>blockQuote>paragraph>$text[bold]', () => {
  863. expect( schema.checkAttribute( r1bQp.getChild( 0 ), 'bold' ) ).to.be.true;
  864. } );
  865. it( 'accepts attribute $root>listItem[alignment]', () => {
  866. expect( schema.checkAttribute( r1lI, 'alignment' ) ).to.be.true;
  867. } );
  868. it( 'accepts attribute $root>listItem[indent]', () => {
  869. expect( schema.checkAttribute( r1lI, 'indent' ) ).to.be.true;
  870. } );
  871. it( 'accepts attribute $root>listItem[type]', () => {
  872. expect( schema.checkAttribute( r1lI, 'type' ) ).to.be.true;
  873. } );
  874. it( 'accepts attribute $root>image[src]', () => {
  875. expect( schema.checkAttribute( r1i, 'src' ) ).to.be.true;
  876. } );
  877. it( 'accepts attribute $root>image[alt]', () => {
  878. expect( schema.checkAttribute( r1i, 'alt' ) ).to.be.true;
  879. } );
  880. it( 'accepts attribute $root>image>caption>$text[bold]', () => {
  881. expect( schema.checkAttribute( r1i.getChild( 0 ).getChild( 0 ), 'bold' ) ).to.be.true;
  882. } );
  883. it( 'rejects attribute $root[indent]', () => {
  884. expect( schema.checkAttribute( root1, 'indent' ) ).to.be.false;
  885. } );
  886. it( 'rejects attribute $root>paragraph[indent]', () => {
  887. expect( schema.checkAttribute( r1p1, 'indent' ) ).to.be.false;
  888. } );
  889. it( 'accepts attribute $root>heading1>$text[bold]', () => {
  890. expect( schema.checkAttribute( r1h.getChild( 0 ), 'bold' ) ).to.be.false;
  891. } );
  892. it( 'rejects attribute $root>paragraph>$text[alignment]', () => {
  893. expect( schema.checkAttribute( r1p1.getChild( 0 ), 'alignment' ) ).to.be.false;
  894. } );
  895. it( 'rejects attribute $root>blockQuote[indent]', () => {
  896. expect( schema.checkAttribute( r1bQ, 'indent' ) ).to.be.false;
  897. } );
  898. it( 'rejects attribute $root>blockQuote[alignment]', () => {
  899. expect( schema.checkAttribute( r1bQ, 'alignment' ) ).to.be.false;
  900. } );
  901. it( 'rejects attribute $root>image[indent]', () => {
  902. expect( schema.checkAttribute( r1i, 'indent' ) ).to.be.false;
  903. } );
  904. it( 'rejects attribute $root>image[alignment]', () => {
  905. expect( schema.checkAttribute( r1i, 'alignment' ) ).to.be.false;
  906. } );
  907. } );
  908. // TODO:
  909. // * getValidRanges
  910. // * checkAttributeInSelection
  911. // * getLimitElement
  912. // * removeDisallowedAttributes
  913. // * isBlock, isLimit, isObject, isRegistered
  914. } );