Skip to content

Commit 0e5e916

Browse files
committed
Basic implementation of entity-id snak types for wbui2025
Add support for entity-id values and entity-id-typed properties in the wbui2025 user interface. Includes support for searching entities and displaying the search results in a lookup / drop-down. Support for WikibaseLexeme - Lexemes, Senses and Forms - is excluded because the extension is not available for all Wikibase installations. Follow-up changes will add that support Depends-On: I660dbbfcc43468c1d9b7f339f4432004559b545b Bug: T403974 Change-Id: I99813bdf9b529892e06356ce84cea5c36a6a6964
1 parent dd6d5a2 commit 0e5e916

21 files changed

+606
-195
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { Util } from 'cypress-wikibase-api';
2+
3+
import { checkA11y } from '../../support/checkA11y';
4+
import { ItemViewPage } from '../../support/pageObjects/ItemViewPage';
5+
import { EditStatementFormPage } from '../../support/pageObjects/EditStatementFormPage';
6+
7+
describe( 'wbui2025 entityId datatypes (item, property)', () => {
8+
9+
const createEntityForDatatype = {
10+
item: ( label: string ) => cy.task( 'MwApi:CreateEntity', {
11+
entityType: 'item',
12+
label: label,
13+
data: { claims: [] },
14+
} ),
15+
property: ( label: string ) => cy.task( 'MwApi:CreateEntity', {
16+
entityType: 'property',
17+
label: label,
18+
data: {
19+
datatype: 'string',
20+
claims: [],
21+
},
22+
} ),
23+
};
24+
25+
for ( const datatype of [ 'item', 'property' ] ) {
26+
context( 'mobile view - ' + datatype + ' datatype', () => {
27+
let propertyName: string;
28+
let entityId: string;
29+
let linkedEntityId: string;
30+
let linkedEntityLabel: string;
31+
let newLinkedEntityLabel: string;
32+
33+
before( () => {
34+
propertyName = Util.getTestString( datatype + '-property' + '-' );
35+
linkedEntityLabel = Util.getTestString( 'linked-' + datatype + '-' );
36+
newLinkedEntityLabel = Util.getTestString( 'new-linked-' + datatype + '-' );
37+
createEntityForDatatype[ datatype ]( linkedEntityLabel ).then( ( newEntityId: string ) => {
38+
linkedEntityId = newEntityId;
39+
} );
40+
createEntityForDatatype[ datatype ]( newLinkedEntityLabel );
41+
cy.task( 'MwApi:CreateProperty', {
42+
label: propertyName,
43+
data: { datatype: 'wikibase-' + datatype },
44+
} ).then( ( newPropertyId: string ) => {
45+
const statementData = {
46+
claims: [ {
47+
mainsnak: {
48+
snaktype: 'value',
49+
property: newPropertyId,
50+
datavalue: {
51+
value: {
52+
'entity-type': datatype,
53+
id: linkedEntityId,
54+
},
55+
type: 'wikibase-entityid',
56+
},
57+
datatype: 'wikibase-' + datatype,
58+
},
59+
type: 'statement',
60+
rank: 'normal',
61+
} ],
62+
};
63+
cy.task( 'MwApi:CreateItem', {
64+
label: Util.getTestString( 'item-with-' + datatype + '-statement' ),
65+
data: statementData,
66+
} ).then( ( newItemId: string ) => {
67+
entityId = newItemId;
68+
} );
69+
} );
70+
} );
71+
72+
beforeEach( () => {
73+
cy.viewport( 375, 1280 );
74+
} );
75+
76+
it( 'displays item statement and supports full editing workflow', () => {
77+
const itemViewPage = new ItemViewPage( entityId );
78+
itemViewPage.open().statementsSection();
79+
checkA11y( ItemViewPage.STATEMENTS );
80+
81+
itemViewPage.editLinks().first().should( 'exist' ).should( 'be.visible' );
82+
itemViewPage.editLinks().first().click();
83+
84+
const editFormPage = new EditStatementFormPage();
85+
editFormPage.formHeading().should( 'exist' );
86+
editFormPage.propertyName().should( 'have.text', propertyName );
87+
88+
editFormPage.lookupComponent()
89+
.should( 'exist' ).should( 'be.visible' );
90+
91+
editFormPage.lookupInput()
92+
.should( 'have.value', linkedEntityLabel );
93+
94+
editFormPage.lookupInput().clear();
95+
editFormPage.lookupInput().type( newLinkedEntityLabel );
96+
editFormPage.lookupInput().focus();
97+
98+
editFormPage.menu().should( 'be.visible' );
99+
100+
editFormPage.menuItems().first().click();
101+
editFormPage.lookupInput().should( 'have.value', newLinkedEntityLabel );
102+
103+
editFormPage.publishButton().click();
104+
105+
/* Wait for the form to close, and check the value is changed */
106+
editFormPage.formHeading().should( 'not.exist' );
107+
itemViewPage.mainSnakValues().first().should( 'contain.text', newLinkedEntityLabel );
108+
109+
} );
110+
} );
111+
}
112+
} );

cypress/e2e/wbui2025/editStringDatatypes.cy.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ describe( 'wbui2025 string datatypes (tabular-data and geo-shape)', () => {
123123
cy.wait( '@commonsSearch' );
124124
editFormPage.menu().should( 'exist' );
125125

126-
editFormPage.menuItems().eq( 3 ).click();
126+
editFormPage.menuItems().eq( 0 ).click();
127127
editFormPage.lookupInput().should( 'have.value', 'Data:Weather_data.tab' );
128128

129129
editFormPage.addValueButtons().first().click();
@@ -141,7 +141,7 @@ describe( 'wbui2025 string datatypes (tabular-data and geo-shape)', () => {
141141
cy.wait( '@commonsSearch' );
142142
editFormPage.menu().should( 'exist' );
143143

144-
editFormPage.menuItems().eq( 11 ).click();
144+
editFormPage.menuItems().eq( 2 ).click();
145145
editFormPage.valueForms()
146146
.last()
147147
.find( editFormPage.getLookupInputSelector() )
@@ -272,7 +272,7 @@ describe( 'wbui2025 string datatypes (tabular-data and geo-shape)', () => {
272272
.should( 'exist' )
273273
.should( 'be.visible' );
274274

275-
editFormPage.menuItems().eq( 3 ).click();
275+
editFormPage.menuItems().eq( 0 ).click();
276276
editFormPage.lookupInput().should( 'have.value', 'Data:Hamburg.map' );
277277

278278
editFormPage.addValueButtons().first().click();
@@ -290,7 +290,7 @@ describe( 'wbui2025 string datatypes (tabular-data and geo-shape)', () => {
290290
cy.wait( '@commonsSearch' );
291291
editFormPage.menu().should( 'exist' );
292292

293-
editFormPage.menuItems().eq( 9 ).click();
293+
editFormPage.menuItems().eq( 1 ).click();
294294
editFormPage.valueForms()
295295
.last()
296296
.find( editFormPage.getLookupInputSelector() )

cypress/support/pageObjects/EditStatementFormPage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export class EditStatementFormPage {
7979
}
8080

8181
public menuItems(): Chainable {
82-
return cy.get( EditStatementFormPage.SELECTORS.MENU_ITEM );
82+
return cy.get( EditStatementFormPage.SELECTORS.MENU_ITEM ).filter( ':visible' );
8383
}
8484

8585
public references(): Chainable {

repo/includes/RepoHooks.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,7 @@ public function onResourceLoaderRegisterModules( $rl ): void {
10181018
'resources/wikibase.wbui2025/store/parsedValueStore.js',
10191019
'resources/wikibase.wbui2025/store/savedStatementsStore.js',
10201020
'resources/wikibase.wbui2025/store/serverRenderedHtml.js',
1021+
'resources/wikibase.wbui2025/store/snakValueStrategies.js',
10211022
[
10221023
'name' => 'resources/wikibase.wbui2025/icons.json',
10231024
'callback' => CodexModule::getIcons( ... ),

repo/resources/wikibase.wbui2025/api/commons.js

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const {
1010
tabularDataStorageApiEndpointUrl,
1111
geoShapeStorageApiEndpointUrl
1212
} = require( '../repoSettings.json' );
13-
const { foreignApi } = require( './api.js' );
13+
const { api, foreignApi } = require( './api.js' );
1414
const tabularDataSearchTerm = 'contentmodel:Tabular.JsonConfig';
1515
const geoShapeDataSearchTerm = 'contentmodel:Map.JsonConfig';
1616

@@ -51,21 +51,37 @@ const searchGeoShapes = function ( searchTerm, offset = 0 ) {
5151
};
5252

5353
/**
54-
* Generic search function that routes to appropriate search method based on datatype
54+
* Search the repo for entities with a matching label
5555
*
56-
* @param {string} datatype The datatype to search for
57-
* @param {string} searchTerm The search term
58-
* @param {number} offset Optional result offset for pagination
59-
* @return {Promise<Object>} Promise resolving to search results
56+
* @param {string} searchTerm
57+
* @param {string} entityType
58+
* @returns {Promise<*>}
59+
*/
60+
const searchForEntities = async function ( searchTerm, entityType ) {
61+
return api.get( api.assertCurrentUser( {
62+
action: 'wbsearchentities',
63+
search: searchTerm,
64+
type: entityType,
65+
language: mw.config.get( 'wgUserLanguage' )
66+
} ) ).then( ( response ) => response.search );
67+
};
68+
69+
/**
70+
* Transform entity search results into menu items format
71+
*
72+
* @param {Array} searchResults Array of search results from API
73+
* @return {Array} Array of menu items with label, value, and description
6074
*/
61-
const searchByDatatype = function ( datatype, searchTerm, offset = 0 ) {
62-
if ( datatype === 'tabular-data' ) {
63-
return searchTabularData( searchTerm, offset );
64-
} else if ( datatype === 'geo-shape' ) {
65-
return searchGeoShapes( searchTerm, offset );
75+
const transformEntitySearchResults = function ( searchResults ) {
76+
if ( !searchResults || searchResults.length === 0 ) {
77+
return [];
6678
}
6779

68-
throw new Error( `Unsupported datatype: ${ datatype }` );
80+
return searchResults.map( ( result ) => ( {
81+
label: result.label,
82+
value: result.id,
83+
description: result.description
84+
} ) );
6985
};
7086

7187
/**
@@ -87,8 +103,9 @@ const transformSearchResults = function ( searchResults ) {
87103
};
88104

89105
module.exports = {
106+
searchForEntities,
90107
searchTabularData,
91108
searchGeoShapes,
92-
searchByDatatype,
93-
transformSearchResults
109+
transformSearchResults,
110+
transformEntitySearchResults
94111
};

repo/resources/wikibase.wbui2025/api/editEntity.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ const updateStatements = async function ( entityId, statements ) {
1313
};
1414

1515
/**
16+
* @param {string} generate
1617
* @param {Object} dataValue
1718
* @param {string|null} propertyId
1819
*/
19-
const renderSnakValueHtml = async function ( dataValue, propertyId = null ) {
20+
const renderSnakValue = async function ( generate, dataValue, propertyId = null ) {
2021
const params = {
2122
action: 'wbformatvalue',
22-
generate: 'text/html; disposition=verbose-preview',
23+
generate: generate,
2324
datavalue: JSON.stringify( dataValue )
2425
};
2526

@@ -29,7 +30,22 @@ const renderSnakValueHtml = async function ( dataValue, propertyId = null ) {
2930

3031
const fetchResult = await api.get( params );
3132
return fetchResult.result;
33+
};
3234

35+
/**
36+
* @param {Object} dataValue
37+
* @param {string|null} propertyId
38+
*/
39+
const renderSnakValueHtml = async function ( dataValue, propertyId = null ) {
40+
return renderSnakValue( 'text/html; disposition=verbose-preview', dataValue, propertyId );
41+
};
42+
43+
/**
44+
* @param {Object} dataValue
45+
* @param {string|null} propertyId
46+
*/
47+
const renderSnakValueText = async function ( dataValue, propertyId = null ) {
48+
return renderSnakValue( 'text/plain', dataValue, propertyId );
3349
};
3450

3551
const renderPropertyLinkHtml = async function ( propertyId ) {
@@ -44,18 +60,17 @@ const renderPropertyLinkHtml = async function ( propertyId ) {
4460
/**
4561
* Parse a given input into a full data value.
4662
*
47-
* @param {string} propertyId
4863
* @param {string} input
64+
* @param {Object} parseOptions
4965
* @returns {Promise<Object|null>} A data value object (with "type" and "value" keys),
5066
* or null if the input could not be parsed successfully.
5167
*/
52-
const parseValue = async function ( propertyId, input ) {
68+
const parseValue = async function ( input, parseOptions = {} ) {
5369
try {
54-
const { results } = await api.get( {
70+
const { results } = await api.get( Object.assign( {
5571
action: 'wbparsevalue',
56-
property: propertyId,
5772
values: [ input ]
58-
} );
73+
}, parseOptions ) );
5974
return results.find( ( result ) => result.raw === input );
6075
} catch ( e ) {
6176
return null;
@@ -65,6 +80,7 @@ const parseValue = async function ( propertyId, input ) {
6580
module.exports = {
6681
updateStatements,
6782
renderSnakValueHtml,
83+
renderSnakValueText,
6884
renderPropertyLinkHtml,
6985
parseValue
7086
};

0 commit comments

Comments
 (0)