schema2.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056
  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( 'checkChild()', () => {
  159. describe( 'allowIn cases', () => {
  160. it( 'passes $root>paragraph', () => {
  161. schema.register( '$root' );
  162. schema.register( 'paragraph', {
  163. allowIn: '$root'
  164. } );
  165. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  166. } );
  167. it( 'passes $root>paragraph and $root2>paragraph – support for array values', () => {
  168. schema.register( '$root' );
  169. schema.register( '$root2' );
  170. schema.register( 'paragraph', {
  171. allowIn: [ '$root', '$root2' ]
  172. } );
  173. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  174. expect( schema.checkChild( root2, r1p1 ) ).to.be.true;
  175. } );
  176. it( 'passes $root>paragraph[align] – attributes does not matter', () => {
  177. schema.register( '$root' );
  178. schema.register( 'paragraph', {
  179. allowIn: '$root'
  180. } );
  181. expect( schema.checkChild( root1, r1p2 ) ).to.be.true;
  182. } );
  183. it( 'passes $root>div>div – in case of circular refs', () => {
  184. schema.register( '$root' );
  185. schema.register( 'div', {
  186. allowIn: [ '$root', 'div' ]
  187. } );
  188. const div = new Element( 'div' );
  189. root1.appendChildren( div );
  190. const div2 = new Element( 'div' );
  191. expect( schema.checkChild( div, div2 ) ).to.be.true;
  192. } );
  193. it( 'passes $root>div>div – in case of circular refs, when div1==div2', () => {
  194. schema.register( '$root' );
  195. schema.register( 'div', {
  196. allowIn: [ '$root', 'div' ]
  197. } );
  198. const div = new Element( 'div' );
  199. root1.appendChildren( div );
  200. expect( schema.checkChild( div, div ) ).to.be.true;
  201. } );
  202. it( 'rejects $root>paragraph – unregistered paragraph', () => {
  203. schema.register( '$root' );
  204. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  205. } );
  206. it( 'rejects $root>paragraph – registered different item', () => {
  207. schema.register( '$root' );
  208. schema.register( 'paragraph' );
  209. schema.register( 'listItem', {
  210. allowIn: '$root'
  211. } );
  212. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  213. } );
  214. it( 'rejects $root>paragraph – paragraph allowed in different context', () => {
  215. schema.register( '$root' );
  216. schema.register( '$fancyRoot' );
  217. schema.register( 'paragraph', {
  218. allowIn: '$fancyRoot'
  219. } );
  220. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  221. } );
  222. it( 'rejects $root>blockQuote>paragraph – since paragraph is only allowed in $root', () => {
  223. schema.register( '$root' );
  224. schema.register( 'paragraph', {
  225. allowIn: '$root'
  226. } );
  227. expect( schema.checkChild( r1bQ, r1bQp ) ).to.be.false;
  228. } );
  229. it( 'rejects $root>blockQuote>paragraph – since paragraph is only allowed in $root v2', () => {
  230. schema.register( '$root' );
  231. schema.register( 'blockQuote', {
  232. allowIn: '$root'
  233. } );
  234. schema.register( 'paragraph', {
  235. allowIn: '$root'
  236. } );
  237. expect( schema.checkChild( r1bQ, r1bQp ) ).to.be.false;
  238. } );
  239. it( 'rejects $root>blockQuote>paragraph>$text - since paragraph is not allowed in blockQuote', () => {
  240. schema.register( '$root' );
  241. schema.register( 'paragraph', {
  242. allowIn: '$root'
  243. } );
  244. schema.register( '$text', {
  245. allowIn: 'paragraph'
  246. } );
  247. expect( schema.checkChild( root1, r1bQp.getChild( 0 ) ) ).to.be.false;
  248. } );
  249. it( 'rejects $root>blockQuote>paragraph>$text - since blockQuote is not allowed in $root', () => {
  250. schema.register( '$root' );
  251. schema.register( 'blockQuote' );
  252. schema.register( 'paragraph', {
  253. allowIn: [ 'blockQuote', '$root' ]
  254. } );
  255. schema.register( '$text', {
  256. allowIn: 'paragraph'
  257. } );
  258. expect( schema.checkChild( root1, r1bQp.getChild( 0 ) ) ).to.be.false;
  259. } );
  260. } );
  261. describe( 'allowWhere cases', () => {
  262. it( 'passes $root>paragraph – paragraph inherits from $block', () => {
  263. schema.register( '$root' );
  264. schema.register( '$block', {
  265. allowIn: '$root'
  266. } );
  267. schema.register( 'paragraph', {
  268. allowWhere: '$block'
  269. } );
  270. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  271. } );
  272. it( 'supports the array syntax', () => {
  273. schema.register( '$root' );
  274. schema.register( '$root2' );
  275. schema.register( '$block', {
  276. allowIn: '$root'
  277. } );
  278. schema.register( '$block2', {
  279. allowIn: '$root2'
  280. } );
  281. schema.register( 'paragraph', {
  282. allowWhere: [ '$block', '$block2' ]
  283. } );
  284. expect( schema.checkChild( root1, r1p1 ), '$root' ).to.be.true;
  285. expect( schema.checkChild( root2, r1p1 ), '$root2' ).to.be.true;
  286. } );
  287. // This checks if some inapropriate caching or preprocessing isn't applied by register().
  288. it( 'passes $root>paragraph – paragraph inherits from $block, order of rules does not matter', () => {
  289. schema.register( '$root' );
  290. schema.register( 'paragraph', {
  291. allowWhere: '$block'
  292. } );
  293. schema.register( '$block', {
  294. allowIn: '$root'
  295. } );
  296. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  297. } );
  298. it( 'passes $root>paragraph – paragraph inherits from $specialBlock which inherits from $block', () => {
  299. schema.register( '$root' );
  300. schema.register( '$block', {
  301. allowIn: '$root'
  302. } );
  303. schema.register( '$specialBlock', {
  304. allowWhere: '$block'
  305. } );
  306. schema.register( 'paragraph', {
  307. allowWhere: '$specialBlock'
  308. } );
  309. expect( schema.checkChild( root1, r1p1 ) ).to.be.true;
  310. } );
  311. it( 'rejects $root>paragraph – paragraph inherits from $block but $block is not allowed in $root', () => {
  312. schema.register( '$root' );
  313. schema.register( '$block' );
  314. schema.register( 'paragraph', {
  315. allowWhere: '$block'
  316. } );
  317. expect( schema.checkChild( root1, r1p1 ) ).to.be.false;
  318. } );
  319. it( 'rejects $root>paragraph>$text – paragraph inherits from $block but $block is not allowed in $root', () => {
  320. schema.register( '$root' );
  321. schema.register( '$block' );
  322. schema.register( 'paragraph', {
  323. allowWhere: '$block'
  324. } );
  325. schema.register( '$text', {
  326. allowIn: 'paragraph'
  327. } );
  328. expect( schema.checkChild( root1, r1p1.getChild( 0 ) ) ).to.be.false;
  329. } );
  330. } );
  331. describe( 'allowContentOf cases', () => {
  332. it( 'passes $root2>paragraph – $root2 inherits from $root', () => {
  333. schema.register( '$root' );
  334. schema.register( 'paragraph', {
  335. allowIn: '$root'
  336. } );
  337. schema.register( '$root2', {
  338. allowContentOf: '$root'
  339. } );
  340. expect( schema.checkChild( root2, r1p1 ) ).to.be.true;
  341. } );
  342. it( 'supports the array syntax', () => {
  343. schema.register( '$root' );
  344. schema.register( '$root2' );
  345. schema.register( 'paragraph', {
  346. allowIn: '$root'
  347. } );
  348. schema.register( 'heading1', {
  349. allowIn: '$root2'
  350. } );
  351. schema.register( '$root3', {
  352. allowContentOf: [ '$root', '$root2' ]
  353. } );
  354. const root3 = new Element( '$root3' );
  355. const heading1 = new Element( 'heading1' );
  356. expect( schema.checkChild( root3, r1p1 ), 'paragraph' ).to.be.true;
  357. expect( schema.checkChild( root3, heading1 ), 'heading1' ).to.be.true;
  358. } );
  359. it( 'passes $root2>paragraph – $root2 inherits from $root, order of rules does not matter', () => {
  360. schema.register( '$root' );
  361. schema.register( '$root2', {
  362. allowContentOf: '$root'
  363. } );
  364. schema.register( 'paragraph', {
  365. allowIn: '$root'
  366. } );
  367. expect( schema.checkChild( root2, r1p1 ) ).to.be.true;
  368. } );
  369. it( 'passes $root>paragraph>$text – paragraph inherits content of $block', () => {
  370. schema.register( '$root' );
  371. schema.register( '$block' );
  372. schema.register( 'paragraph', {
  373. allowIn: '$root',
  374. allowContentOf: '$block'
  375. } );
  376. schema.register( '$text', {
  377. allowIn: '$block'
  378. } );
  379. expect( schema.checkChild( r1p1, r1p1.getChild( 0 ) ) ).to.be.true;
  380. } );
  381. // TODO we need to make it possible to disallow bQ in bQ.
  382. it( 'passes $root>blockQuote>paragraph – blockQuote inherits content of $root', () => {
  383. schema.register( '$root' );
  384. schema.register( 'blockQuote', {
  385. allowIn: '$root',
  386. allowContentOf: '$root'
  387. } );
  388. schema.register( 'paragraph', {
  389. allowIn: '$root'
  390. } );
  391. expect( schema.checkChild( r1bQ, r1bQp ) ).to.be.true;
  392. } );
  393. it( 'rejects $root2>paragraph – $root2 inherits from $root, but paragraph is not allowed there anyway', () => {
  394. schema.register( '$root' );
  395. schema.register( 'paragraph' );
  396. schema.register( '$root2', {
  397. allowContentOf: '$root'
  398. } );
  399. expect( schema.checkChild( root2, r1p1 ) ).to.be.false;
  400. } );
  401. } );
  402. describe( 'mix of allowContentOf and allowWhere', () => {
  403. it( 'passes $root>paragraph>$text – paragraph inherits all from $block', () => {
  404. schema.register( '$root' );
  405. schema.register( '$block', {
  406. allowIn: '$root'
  407. } );
  408. schema.register( 'paragraph', {
  409. allowContentOf: '$block',
  410. allowWhere: '$block'
  411. } );
  412. schema.register( '$text', {
  413. allowIn: '$block'
  414. } );
  415. expect( schema.checkChild( r1p1, r1p1.getChild( 0 ) ) ).to.be.true;
  416. } );
  417. it( 'passes $root>paragraph and $root2>paragraph – where $root2 inherits content of $root' +
  418. 'and paragraph inherits allowWhere from $block', () => {
  419. schema.register( '$root' );
  420. schema.register( '$root2', {
  421. allowContentOf: '$root'
  422. } );
  423. schema.register( '$block', {
  424. allowIn: '$root'
  425. } );
  426. schema.register( 'paragraph', {
  427. allowWhere: '$block'
  428. } );
  429. expect( schema.checkChild( root1, 'paragraph' ), 'root1' ).to.be.true;
  430. expect( schema.checkChild( root2, 'paragraph' ), 'root2' ).to.be.true;
  431. } );
  432. it( 'passes d>a where d inherits content of c which inherits content of b', () => {
  433. schema.register( 'b' );
  434. schema.register( 'a', { allowIn: 'b' } );
  435. schema.register( 'c', { allowContentOf: 'b' } );
  436. schema.register( 'd', { allowContentOf: 'c' } );
  437. const d = new Element( 'd' );
  438. expect( schema.checkChild( d, 'a' ) ).to.be.true;
  439. } );
  440. // This case won't pass becuase we compile the rules in a pretty naive way.
  441. // To make chains like this work we'd need to repeat compilation of allowContentOf rules
  442. // as long as the previous iteration found something to compile.
  443. // This way, even though we'd not compile d<-c in the first run, we'd still find b<-c
  444. // and since we've found something, we'd now try d<-c which would work.
  445. //
  446. // We ignore those situations for now as they are very unlikely to happen and would
  447. // significantly raised the complexity of rule compilation.
  448. //
  449. // it( 'passes d>a where d inherits content of c which inherits content of b', () => {
  450. // schema.register( 'b' );
  451. // schema.register( 'a', { allowIn: 'b' } );
  452. // schema.register( 'd', { allowContentOf: 'c' } );
  453. // schema.register( 'c', { allowContentOf: 'b' } );
  454. //
  455. // const d = new Element( 'd' );
  456. //
  457. // expect( schema.checkChild( d, 'a' ) ).to.be.true;
  458. // } );
  459. } );
  460. // We need to handle cases where some independent features registered rules which might use
  461. // optional elements (elements which might not have been registered).
  462. describe( 'missing rules', () => {
  463. it( 'does not break when trying to check a child which is not registered', () => {
  464. schema.register( '$root' );
  465. expect( schema.checkChild( root1, 'foo404' ) ).to.be.false;
  466. } );
  467. it( 'does not break when trying to check registered child in a context which contains unregistered elements', () => {
  468. const foo404 = new Element( 'foo404' );
  469. root1.appendChildren( foo404 );
  470. schema.register( '$root' );
  471. schema.register( '$text', {
  472. allowIn: '$root'
  473. } );
  474. expect( schema.checkChild( foo404, '$text' ) ).to.be.false;
  475. } );
  476. it( 'does not break when used allowedIn pointing to an unregistered element', () => {
  477. schema.register( '$root' );
  478. schema.register( '$text', {
  479. allowIn: 'foo404'
  480. } );
  481. expect( schema.checkChild( root1, '$text' ) ).to.be.false;
  482. } );
  483. it( 'does not break when used allowWhere pointing to an unregistered element', () => {
  484. schema.register( '$root' );
  485. schema.register( '$text', {
  486. allowWhere: 'foo404'
  487. } );
  488. expect( schema.checkChild( root1, '$text' ) ).to.be.false;
  489. } );
  490. it( 'does not break when used allowContentOf pointing to an unregistered element', () => {
  491. schema.register( '$root', {
  492. allowContentOf: 'foo404'
  493. } );
  494. schema.register( '$text', {
  495. allowIn: '$root'
  496. } );
  497. expect( schema.checkChild( root1, '$text' ) ).to.be.true;
  498. } );
  499. it( 'checks whether allowIn uses a registered element', () => {
  500. schema.register( 'paragraph', {
  501. allowIn: '$root'
  502. } );
  503. // $root isn't registered!
  504. expect( schema.checkChild( root1, 'paragraph' ) ).to.be.false;
  505. } );
  506. } );
  507. } );
  508. describe( 'checkAttribute()', () => {
  509. describe( 'allowAttributes', () => {
  510. it( 'passes paragraph[align]', () => {
  511. schema.register( 'paragraph', {
  512. allowAttributes: 'align'
  513. } );
  514. expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.true;
  515. } );
  516. it( 'passes paragraph[align] and paragraph[dir] – support for array values', () => {
  517. schema.register( 'paragraph', {
  518. allowAttributes: [ 'align', 'dir' ]
  519. } );
  520. expect( schema.checkAttribute( r1p1, 'align' ), 'align' ).to.be.true;
  521. expect( schema.checkAttribute( r1p1, 'dir' ), 'dir' ).to.be.true;
  522. } );
  523. it( 'passes paragraph>$text[bold]', () => {
  524. schema.register( 'paragraph' );
  525. schema.register( '$text', {
  526. allowIn: 'paragraph',
  527. allowAttributes: 'bold'
  528. } );
  529. expect( schema.checkAttribute( r1p1.getChild( 0 ), 'bold' ) ).to.be.true;
  530. } );
  531. } );
  532. describe( 'allowAttributesOf', () => {
  533. it( 'passes paragraph[align] – paragraph inherits from $block', () => {
  534. schema.register( '$block', {
  535. allowAttributes: 'align'
  536. } );
  537. schema.register( 'paragraph', {
  538. allowAttributesOf: '$block'
  539. } );
  540. expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.true;
  541. } );
  542. it( 'passes paragraph[align] and paragraph[dir] – support for array values', () => {
  543. schema.register( '$block', {
  544. allowAttributes: 'align'
  545. } );
  546. schema.register( '$block2', {
  547. allowAttributes: 'dir'
  548. } );
  549. schema.register( 'paragraph', {
  550. allowAttributesOf: [ '$block', '$block2' ]
  551. } );
  552. expect( schema.checkAttribute( r1p1, 'align' ), 'align' ).to.be.true;
  553. expect( schema.checkAttribute( r1p1, 'dir' ), 'dir' ).to.be.true;
  554. } );
  555. it( 'passes paragraph[align] and paragraph[dir] – support for combined allowAttributes and allowAttributesOf', () => {
  556. schema.register( '$block', {
  557. allowAttributes: 'align'
  558. } );
  559. schema.register( 'paragraph', {
  560. allowAttributes: 'dir',
  561. allowAttributesOf: '$block'
  562. } );
  563. expect( schema.checkAttribute( r1p1, 'align' ), 'align' ).to.be.true;
  564. expect( schema.checkAttribute( r1p1, 'dir' ), 'dir' ).to.be.true;
  565. } );
  566. // The support for allowAttributesOf is broken in the similar way as for allowContentOf (see the comment above).
  567. // However, those situations are rather theoretical, so we're not going to waste time on them now.
  568. } );
  569. describe( 'missing rules', () => {
  570. it( 'does not crash when checking an attribute of a unregistered element', () => {
  571. expect( schema.checkAttribute( r1p1, 'align' ) ).to.be.false;
  572. } );
  573. it( 'does not crash when checking when inheriting attributes of a unregistered element', () => {
  574. schema.register( 'paragraph', {
  575. allowAttributesOf: '$block'
  576. } );
  577. expect( schema.checkAttribute( r1p1, 'whatever' ) ).to.be.false;
  578. } );
  579. } );
  580. } );
  581. describe( 'real scenarios', () => {
  582. let r1bQi, r1i, r1lI, r1h, r1bQlI;
  583. const rules = [
  584. () => {
  585. schema.register( 'paragraph', {
  586. allowWhere: '$block',
  587. allowContentOf: '$block',
  588. allowAttributesOf: '$block'
  589. } );
  590. },
  591. () => {
  592. schema.register( 'heading1', {
  593. allowWhere: '$block',
  594. allowContentOf: '$block',
  595. allowAttributesOf: '$block'
  596. } );
  597. },
  598. () => {
  599. schema.register( 'listItem', {
  600. allowWhere: '$block',
  601. allowContentOf: '$block',
  602. allowAttributes: [ 'indent', 'type' ],
  603. allowAttributesOf: '$block'
  604. } );
  605. },
  606. () => {
  607. schema.register( 'blockQuote', {
  608. allowWhere: '$block',
  609. allowContentOf: '$root'
  610. } );
  611. // Disallow blockQuote in blockQuote.
  612. schema.on( 'checkChild', ( evt, args ) => {
  613. const context = args[ 0 ];
  614. const child = args[ 1 ];
  615. const childRule = schema.getRule( child );
  616. if ( childRule.name == 'blockQuote' && context[ context.length - 1 ].name == 'blockQuote' ) {
  617. evt.stop();
  618. evt.return = false;
  619. }
  620. }, { priority: 'high' } );
  621. },
  622. () => {
  623. schema.register( 'image', {
  624. allowWhere: '$block',
  625. allowAttributes: [ 'src', 'alt' ]
  626. } );
  627. },
  628. () => {
  629. schema.register( 'caption', {
  630. allowIn: 'image',
  631. allowContentOf: '$block'
  632. } );
  633. },
  634. () => {
  635. schema.extend( '$text', {
  636. allowAttributes: [ 'bold', 'italic' ]
  637. } );
  638. // Disallow bold in heading1.
  639. schema.on( 'checkAttribute', ( evt, args ) => {
  640. const context = args[ 0 ];
  641. const contextLast = context[ context.length - 1 ];
  642. const contextSecondToLast = context[ context.length - 2 ];
  643. const attributeName = args[ 1 ];
  644. if ( contextLast.name == '$text' && contextSecondToLast.name == 'heading1' && attributeName == 'bold' ) {
  645. evt.stop();
  646. evt.return = false;
  647. }
  648. }, { priority: 'high' } );
  649. },
  650. () => {
  651. schema.extend( '$block', {
  652. allowAttributes: 'alignment'
  653. } );
  654. }
  655. ];
  656. beforeEach( () => {
  657. schema.register( '$root' );
  658. schema.register( '$block', {
  659. allowIn: '$root'
  660. } );
  661. schema.register( '$text', {
  662. allowIn: '$block'
  663. } );
  664. for ( const rule of rules ) {
  665. rule();
  666. }
  667. // or...
  668. //
  669. // Use the below code to shuffle the rules.
  670. // Don't look here, Szymon!
  671. //
  672. // const rulesCopy = rules.slice();
  673. //
  674. // while ( rulesCopy.length ) {
  675. // const r = Math.floor( Math.random() * rulesCopy.length );
  676. // rulesCopy.splice( r, 1 )[ 0 ]();
  677. // }
  678. root1 = new Element( '$root', null, [
  679. new Element( 'paragraph', null, 'foo' ),
  680. new Element( 'paragraph', { alignment: 'right' }, 'bar' ),
  681. new Element( 'listItem', { type: 'x', indent: 0 }, 'foo' ),
  682. new Element( 'heading1', null, 'foo' ),
  683. new Element( 'blockQuote', null, [
  684. new Element( 'paragraph', null, 'foo' ),
  685. new Element( 'listItem', { type: 'x', indent: 0 }, 'foo' ),
  686. new Element( 'image', null, [
  687. new Element( 'caption', null, 'foo' )
  688. ] )
  689. ] ),
  690. new Element( 'image', null, [
  691. new Element( 'caption', null, 'foo' )
  692. ] )
  693. ] );
  694. r1p1 = root1.getChild( 0 );
  695. r1p2 = root1.getChild( 1 );
  696. r1lI = root1.getChild( 2 );
  697. r1h = root1.getChild( 3 );
  698. r1bQ = root1.getChild( 4 );
  699. r1i = root1.getChild( 5 );
  700. r1bQp = r1bQ.getChild( 0 );
  701. r1bQlI = r1bQ.getChild( 1 );
  702. r1bQi = r1bQ.getChild( 2 );
  703. } );
  704. it( 'passes $root>paragraph', () => {
  705. expect( schema.checkChild( root1, 'paragraph' ) ).to.be.true;
  706. } );
  707. it( 'passes $root>paragraph>$text', () => {
  708. expect( schema.checkChild( r1p1, '$text' ), 'paragraph' ).to.be.true;
  709. expect( schema.checkChild( r1p2, '$text' ), 'paragraph[alignment]' ).to.be.true;
  710. } );
  711. it( 'passes $root>listItem', () => {
  712. expect( schema.checkChild( root1, 'listItem' ) ).to.be.true;
  713. } );
  714. it( 'passes $root>listItem>$text', () => {
  715. expect( schema.checkChild( r1lI, '$text' ) ).to.be.true;
  716. } );
  717. it( 'passes $root>blockQuote>paragraph', () => {
  718. expect( schema.checkChild( r1bQ, 'paragraph' ) ).to.be.true;
  719. } );
  720. it( 'passes $root>blockQuote>paragraph>$text', () => {
  721. expect( schema.checkChild( r1bQp, '$text' ) ).to.be.true;
  722. } );
  723. it( 'passes $root>blockQuote>listItem', () => {
  724. expect( schema.checkChild( r1bQ, 'listItem' ) ).to.be.true;
  725. } );
  726. it( 'passes $root>blockQuote>listItem>$text', () => {
  727. expect( schema.checkChild( r1bQlI, '$text' ) ).to.be.true;
  728. } );
  729. it( 'passes $root>blockQuote>image', () => {
  730. expect( schema.checkChild( r1bQ, 'image' ) ).to.be.true;
  731. } );
  732. it( 'passes $root>blockQuote>image>caption', () => {
  733. expect( schema.checkChild( r1bQi, 'caption' ) ).to.be.true;
  734. } );
  735. it( 'passes $root>blockQuote>image>caption>$text', () => {
  736. expect( schema.checkChild( r1bQi.getChild( 0 ), '$text' ) ).to.be.true;
  737. } );
  738. it( 'passes $root>image', () => {
  739. expect( schema.checkChild( root1, 'image' ) ).to.be.true;
  740. } );
  741. it( 'passes $root>image>caption', () => {
  742. expect( schema.checkChild( r1i, 'caption' ) ).to.be.true;
  743. } );
  744. it( 'passes $root>image>caption>$text', () => {
  745. expect( schema.checkChild( r1i.getChild( 0 ), '$text' ) ).to.be.true;
  746. } );
  747. it( 'rejects $root>$root', () => {
  748. expect( schema.checkChild( root1, '$root' ) ).to.be.false;
  749. } );
  750. it( 'rejects $root>$text', () => {
  751. expect( schema.checkChild( root1, '$text' ) ).to.be.false;
  752. } );
  753. it( 'rejects $root>caption', () => {
  754. expect( schema.checkChild( root1, 'caption' ) ).to.be.false;
  755. } );
  756. it( 'rejects $root>paragraph>paragraph', () => {
  757. expect( schema.checkChild( r1p1, 'paragraph' ) ).to.be.false;
  758. } );
  759. it( 'rejects $root>paragraph>paragraph>$text', () => {
  760. // Edge case because p>p should not exist in the first place.
  761. // But it's good to know that it blocks also this.
  762. const p = new Element( 'p' );
  763. r1p1.appendChildren( p );
  764. expect( schema.checkChild( p, '$text' ) ).to.be.false;
  765. } );
  766. it( 'rejects $root>paragraph>$block', () => {
  767. expect( schema.checkChild( r1p1, '$block' ) ).to.be.false;
  768. } );
  769. it( 'rejects $root>paragraph>blockQuote', () => {
  770. expect( schema.checkChild( r1p1, 'blockQuote' ) ).to.be.false;
  771. } );
  772. it( 'rejects $root>paragraph>image', () => {
  773. expect( schema.checkChild( r1p1, 'image' ) ).to.be.false;
  774. } );
  775. it( 'rejects $root>paragraph>caption', () => {
  776. expect( schema.checkChild( r1p1, 'caption' ) ).to.be.false;
  777. } );
  778. it( 'rejects $root>blockQuote>blockQuote', () => {
  779. expect( schema.checkChild( r1bQ, 'blockQuote' ) ).to.be.false;
  780. } );
  781. it( 'rejects $root>blockQuote>caption', () => {
  782. expect( schema.checkChild( r1p1, 'image' ) ).to.be.false;
  783. } );
  784. it( 'rejects $root>blockQuote>$text', () => {
  785. expect( schema.checkChild( r1bQ, '$text' ) ).to.be.false;
  786. } );
  787. it( 'rejects $root>image>$text', () => {
  788. expect( schema.checkChild( r1i, '$text' ) ).to.be.false;
  789. } );
  790. it( 'rejects $root>image>paragraph', () => {
  791. expect( schema.checkChild( r1i, 'paragraph' ) ).to.be.false;
  792. } );
  793. it( 'rejects $root>image>caption>paragraph', () => {
  794. expect( schema.checkChild( r1i.getChild( 0 ), 'paragraph' ) ).to.be.false;
  795. } );
  796. it( 'rejects $root>image>caption>blockQuote', () => {
  797. expect( schema.checkChild( r1i.getChild( 0 ), 'blockQuote' ) ).to.be.false;
  798. } );
  799. it( 'accepts attribute $root>paragraph[alignment]', () => {
  800. expect( schema.checkAttribute( r1p1, 'alignment' ) ).to.be.true;
  801. } );
  802. it( 'accepts attribute $root>paragraph>$text[bold]', () => {
  803. expect( schema.checkAttribute( r1p1.getChild( 0 ), 'bold' ) ).to.be.true;
  804. } );
  805. it( 'accepts attribute $root>heading1>$text[italic]', () => {
  806. expect( schema.checkAttribute( r1h.getChild( 0 ), 'italic' ) ).to.be.true;
  807. } );
  808. it( 'accepts attribute $root>blockQuote>paragraph>$text[bold]', () => {
  809. expect( schema.checkAttribute( r1bQp.getChild( 0 ), 'bold' ) ).to.be.true;
  810. } );
  811. it( 'accepts attribute $root>listItem[alignment]', () => {
  812. expect( schema.checkAttribute( r1lI, 'alignment' ) ).to.be.true;
  813. } );
  814. it( 'accepts attribute $root>listItem[indent]', () => {
  815. expect( schema.checkAttribute( r1lI, 'indent' ) ).to.be.true;
  816. } );
  817. it( 'accepts attribute $root>listItem[type]', () => {
  818. expect( schema.checkAttribute( r1lI, 'type' ) ).to.be.true;
  819. } );
  820. it( 'accepts attribute $root>image[src]', () => {
  821. expect( schema.checkAttribute( r1i, 'src' ) ).to.be.true;
  822. } );
  823. it( 'accepts attribute $root>image[alt]', () => {
  824. expect( schema.checkAttribute( r1i, 'alt' ) ).to.be.true;
  825. } );
  826. it( 'accepts attribute $root>image>caption>$text[bold]', () => {
  827. expect( schema.checkAttribute( r1i.getChild( 0 ).getChild( 0 ), 'bold' ) ).to.be.true;
  828. } );
  829. it( 'rejects attribute $root[indent]', () => {
  830. expect( schema.checkAttribute( root1, 'indent' ) ).to.be.false;
  831. } );
  832. it( 'rejects attribute $root>paragraph[indent]', () => {
  833. expect( schema.checkAttribute( r1p1, 'indent' ) ).to.be.false;
  834. } );
  835. it( 'accepts attribute $root>heading1>$text[bold]', () => {
  836. expect( schema.checkAttribute( r1h.getChild( 0 ), 'bold' ) ).to.be.false;
  837. } );
  838. it( 'rejects attribute $root>paragraph>$text[alignment]', () => {
  839. expect( schema.checkAttribute( r1p1.getChild( 0 ), 'alignment' ) ).to.be.false;
  840. } );
  841. it( 'rejects attribute $root>blockQuote[indent]', () => {
  842. expect( schema.checkAttribute( r1bQ, 'indent' ) ).to.be.false;
  843. } );
  844. it( 'rejects attribute $root>blockQuote[alignment]', () => {
  845. expect( schema.checkAttribute( r1bQ, 'alignment' ) ).to.be.false;
  846. } );
  847. it( 'rejects attribute $root>image[indent]', () => {
  848. expect( schema.checkAttribute( r1i, 'indent' ) ).to.be.false;
  849. } );
  850. it( 'rejects attribute $root>image[alignment]', () => {
  851. expect( schema.checkAttribute( r1i, 'alignment' ) ).to.be.false;
  852. } );
  853. } );
  854. // TODO:
  855. // * getValidRanges
  856. // * checkAttributeInSelection
  857. // * getLimitElement
  858. // * removeDisallowedAttributes
  859. // * isBlock, isLimit, isObject, isRegistered
  860. } );