Fix public document edition state
No files matched your search
@@ -10,7 +10,7 @@ Telegram-бот — только часть рабочего окружения
|
||||
## Текущее состояние
|
||||
|
||||
Сейчас реализованы Telegram-бот-секретарь версии `0.2.2`, backend версии
|
||||
`0.13.0` и frontend `0.2.1` с поисковым экраном и внутренней лабораторией.
|
||||
`0.13.0` и frontend `0.2.2` с поисковым экраном и внутренней лабораторией.
|
||||
Авторизация хранится в
|
||||
SQLite; первый пользователь `admin` создаётся интерактивной CLI-командой.
|
||||
Поиск расширяет аббревиатуры организационно-правовых форм и поднимает
|
||||
@@ -20,7 +20,7 @@ SQLite; первый пользователь `admin` создаётся инт
|
||||
|---|---:|---|
|
||||
| Telegram-бот | `0.2.2` | на Synology работает `0.2.1`; обновление после слияния |
|
||||
| Backend | `0.13.0` | авторизация, поиск с фильтрами, ограниченный staging и статические ресурсы |
|
||||
| Frontend | `0.2.1` | поиск по правовой базе, просмотр редакций и внутренняя лаборатория |
|
||||
| Frontend | `0.2.2` | поиск по правовой базе, просмотр редакций и внутренняя лаборатория |
|
||||
| Сбор и обработка правовых данных | `0.8.0` | добавлены relevance set и оценка выдачи |
|
||||
| RAG и база знаний | — | ещё не созданы |
|
||||
|
||||
@@ -63,4 +63,4 @@ python3 -m unittest discover -s tools/telegram-bot -v
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.13.0 · Frontend v0.2.1
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.13.0 · Frontend v0.2.2
|
||||
@@ -296,4 +296,4 @@ PYTHONPATH=backend python3 -m search.query "ЖЧК ачуу тартиби" --la
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Backend v0.13.0 · Frontend v0.2.1
|
||||
Акылдаш · Backend v0.13.0 · Frontend v0.2.2
|
||||
@@ -377,7 +377,7 @@
|
||||
<section class="search__document" id="public-document-panel" aria-labelledby="public-document-heading"><div class="search__document-head"><div class="search__breadcrumbs"><span>Документ</span><span aria-hidden="true">/</span><span id="document-breadcrumb">Выберите результат</span></div><div><button class="search__mobile-back" id="document-back" type="button">К результатам</button><button class="search__expand" id="document-expand" type="button" aria-label="Развернуть документ">Развернуть</button></div><h2 id="public-document-heading">Документ</h2></div><p id="public-document-meta" class="search__document-meta"><span id="document-facts">Выберите результат, чтобы прочитать доступную редакцию.</span><label class="search__document-language">Язык<select id="document-language" disabled aria-label="Язык документа"></select></label></p><div class="search__document-toolbar"><details class="search__toc" id="document-toc"><summary>Содержание</summary><nav aria-label="Содержание документа"><ol id="document-toc-list"><li>Оглавление появится после загрузки документа</li></ol></nav></details><label class="search__edition-label"><select id="document-edition" disabled aria-label="Редакция документа"></select></label><button id="document-print" type="button">Печать</button><a id="document-source" class="search__source-link" href="#" target="_blank" rel="noreferrer" hidden>Официальный источник</a></div><div class="search__find"><label for="document-find">Найти в документе</label><input id="document-find" type="search" placeholder="Фраза в документе" disabled><span class="search__find-count" id="document-find-count" aria-live="polite"></span><button id="document-find-prev" type="button" disabled aria-label="Предыдущее совпадение"><svg viewBox="0 0 20 20" width="18" height="18" aria-hidden="true"><path d="m12 4-6 6 6 6" fill="none" stroke="currentColor" stroke-width="1.8"/></svg></button><button id="document-find-next" type="button" disabled aria-label="Следующее совпадение"><svg viewBox="0 0 20 20" width="18" height="18" aria-hidden="true"><path d="m8 4 6 6-6 6" fill="none" stroke="currentColor" stroke-width="1.8"/></svg></button></div><p class="search__document-note" id="document-note" hidden></p><article id="public-document-body" class="search__document-body"></article></section>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="search__footer"><span><strong>Акылдаш</strong> <span aria-hidden="true">·</span> Концепция Түндүк</span><span>Frontend v0.2.1 <span aria-hidden="true">·</span> Backend v0.13.0</span></footer>
|
||||
<footer class="search__footer"><span><strong>Акылдаш</strong> <span aria-hidden="true">·</span> Концепция Түндүк</span><span>Frontend v0.2.2 <span aria-hidden="true">·</span> Backend v0.13.0</span></footer>
|
||||
<div class="review__actions"><div class="review__actions-inner"><label>Проверяющий<input id="reviewer" readonly aria-label="Проверяющий"></label><label>Комментарий к выдаче <input id="overall-comment" maxlength="4000" placeholder="Что важно учесть? (необязательно)"></label><button class="review__button--primary" id="save" type="button">Сохранить оценку</button></div></div>
|
||||
<dialog id="login-dialog" aria-labelledby="login-heading"><form class="review__page" id="login-form"><h2 id="login-heading">Вход в Акылдаш</h2><p>Введите учетные данные, выданные администратором.</p><label>Логин<input id="login-username" name="username" autocomplete="username" required minlength="3" maxlength="64"></label><label>Пароль<input id="login-password" name="password" type="password" autocomplete="current-password" required></label><p id="login-error" class="review__status review__status--error" role="alert"></p><button class="review__button--primary" type="submit">Войти</button></form></dialog>
|
||||
<dialog id="password-dialog" aria-labelledby="password-heading"><form class="review__page" id="password-form"><h2 id="password-heading">Создайте новый пароль</h2><p class="review__notice">Для продолжения замените временный пароль. Используйте не менее 12 символов.</p><label>Текущий пароль<input id="current-password" type="password" autocomplete="current-password" required></label><label>Новый пароль<input id="new-password" type="password" autocomplete="new-password" minlength="12" maxlength="256" required></label><label>Повторите новый пароль<input id="confirm-password" type="password" autocomplete="new-password" minlength="12" maxlength="256" required></label><p id="password-error" class="review__status review__status--error" role="alert"></p><button class="review__button--primary" type="submit">Сохранить пароль</button></form></dialog>
|
||||
@@ -386,7 +386,7 @@
|
||||
<dialog id="document-dialog"><button class="review__dialog-close" id="dialog-close" type="button">Закрыть</button><h2 id="dialog-heading">Документ</h2><div class="review__document-body" id="dialog-body"></div></dialog>
|
||||
<dialog class="search__about" id="sources-dialog" aria-labelledby="sources-heading"><button class="review__dialog-close" id="sources-close" type="button">Закрыть</button><h2 id="sources-heading">Источники документов</h2><p>Поиск выполняется по документам, подготовленным из правовых источников Кыргызской Республики. Для каждого результата доступная редакция и ссылка на официальный источник показываются в панели документа.</p><p>Официальный источник: <a href="https://cbd.minjust.gov.kg/" target="_blank" rel="noreferrer">Централизованный банк данных правовой информации Министерства юстиции Кыргызской Республики</a>.</p></dialog>
|
||||
<div class="review__tour-spotlight" id="tutorial-spotlight" hidden aria-hidden="true"></div><dialog class="review__tour" id="tutorial-dialog" aria-labelledby="tutorial-heading" aria-describedby="tutorial-text"><div class="review__tour-content"><p class="review__tour-kicker">Короткое знакомство</p><h2 id="tutorial-heading"></h2><p id="tutorial-text"></p><div class="review__tour-footer"><span class="review__tour-progress" id="tutorial-progress"></span><div class="review__tour-actions"><button id="tutorial-skip" type="button">Пропустить</button><button class="review__button--primary" id="tutorial-next" type="button">Далее</button></div></div></div></dialog>
|
||||
<footer class="review__header"><div class="review__header-content">Акылдаш · Frontend v0.2.1 · Backend v0.13.0 · Внутренняя лаборатория релевантности</div></footer>
|
||||
<footer class="review__header"><div class="review__header-content">Акылдаш · Frontend v0.2.2 · Backend v0.13.0 · Внутренняя лаборатория релевантности</div></footer>
|
||||
<script>
|
||||
document.body.classList.add('review__locked');
|
||||
const DRAFT_KEY = 'akyldash-search-review-draft';
|
||||
@@ -414,7 +414,7 @@
|
||||
const modalMessage = (key) => modalCopy[uiLanguage][key];
|
||||
const reviewRoute = location.pathname === '/review';
|
||||
$('review').hidden = !reviewRoute; $('search-page').hidden = reviewRoute; $('review-review-nav').classList.toggle('is-active', reviewRoute); $('review-search-nav').classList.toggle('is-active', !reviewRoute); $('review-nav').classList.toggle('is-active', reviewRoute); $('search-nav').classList.toggle('is-active', !reviewRoute);
|
||||
if (!reviewRoute) { document.title = 'Поиск по правовой базе · Акылдаш'; document.querySelector('.review__header-content').hidden = true; $('tutorial-open').hidden = true; document.querySelector('.review__skip').href = '#public-results'; document.querySelector('footer .review__header-content').textContent = 'Акылдаш · Frontend v0.2.1 · Backend v0.13.0'; }
|
||||
if (!reviewRoute) { document.title = 'Поиск по правовой базе · Акылдаш'; document.querySelector('.review__header-content').hidden = true; $('tutorial-open').hidden = true; document.querySelector('.review__skip').href = '#public-results'; document.querySelector('footer .review__header-content').textContent = 'Акылдаш · Frontend v0.2.2 · Backend v0.13.0'; }
|
||||
async function apiFetch(path, options = {}) { const response = await fetch(path, {credentials: 'same-origin', ...options}); if (response.status === 401 && path !== '/auth/login') { document.body.classList.add('review__locked'); if (!$('login-dialog').open) $('login-dialog').showModal(); $('login-username').focus(); } return response; }
|
||||
const showLogin = () => { document.body.classList.add('review__locked'); if (!$('login-dialog').open) $('login-dialog').showModal(); $('login-username').focus(); };
|
||||
const enterWorkspace = (user) => { account = user; $('signed-in-user').textContent = `${user.username} · ${user.role}`; $('review-signed-in-user').textContent = `${user.username} · ${user.role}`; $('reviewer').value = user.username; $('profile-username').textContent = user.username; $('profile-role').textContent = user.role; $('profile-created').textContent = new Date(user.created_at * 1000).toLocaleDateString('ru-RU'); $('search-users-open').hidden = $('review-users-open').hidden = user.username.toLowerCase() !== 'admin'; document.body.classList.remove('review__locked'); if (!reviewRoute) initSearchPage(); else if (!hasSeenTour()) setTimeout(openTutorial, 350); };
|
||||
@@ -465,7 +465,7 @@
|
||||
input.type = 'radio'; input.name = `filter-${parameter}`; input.value = item.code;
|
||||
input.addEventListener('change', () => runSearch(1));
|
||||
const text = document.createElement('span');
|
||||
text.textContent = `${item.labels.ru || item.labels.ky || item.code} (${item.count})`;
|
||||
text.textContent = `${item.labels[uiLanguage] || item.labels.ru || item.labels.ky || item.code} (${item.count})`;
|
||||
label.append(input, text); group.append(label);
|
||||
});
|
||||
}
|
||||
@@ -619,13 +619,15 @@
|
||||
}
|
||||
async function loadPublicEdition() {
|
||||
if (!selectedPublicDocument) return;
|
||||
const requestId = ++editionRequest;
|
||||
const {code} = selectedPublicDocument;
|
||||
const edition = $('document-edition').value;
|
||||
const language = $('document-language').value;
|
||||
let language = $('document-language').value;
|
||||
const editionInfo = selectedPublicDocument.editions.find((item) => String(item.source_code) === String(edition));
|
||||
const languages = editionInfo?.available_languages || [];
|
||||
if (!languages.includes(language)) { $('document-facts').textContent = copy('editionMissing'); $('public-document-body').replaceChildren(); return; }
|
||||
const requestId = ++editionRequest;
|
||||
if (!languages.includes(language) && languages.length) { language = languages[0]; $('document-language').value = language; }
|
||||
currentDocumentHtml = ''; $('public-document-body').replaceChildren();
|
||||
if (!languages.includes(language)) { $('document-facts').textContent = copy('editionMissing'); return; }
|
||||
$('document-facts').textContent = copy('loadingEdition');
|
||||
try {
|
||||
const response = await apiFetch(`/documents/${encodeURIComponent(code)}/editions/${encodeURIComponent(edition)}?language=${encodeURIComponent(language)}`);
|
||||
@@ -646,13 +648,14 @@
|
||||
highlightDocumentMatches();
|
||||
} catch (error) {
|
||||
if (requestId !== editionRequest) return;
|
||||
currentDocumentHtml = '';
|
||||
$('document-facts').textContent = error.message || copy('editionUnavailable');
|
||||
$('public-document-body').replaceChildren();
|
||||
}
|
||||
}
|
||||
async function openPublicDocument(result, row) {
|
||||
document.querySelectorAll('.search__result--selected').forEach((item) => item.classList.remove('search__result--selected'));
|
||||
row.classList.add('search__result--selected'); selectedPublicDocument = result; ++editionRequest;
|
||||
row.classList.add('search__result--selected'); selectedPublicDocument = result; ++editionRequest; currentDocumentHtml = '';
|
||||
row.querySelector('.search__opened')?.remove();
|
||||
const opened = document.createElement('span'); opened.className = 'search__opened'; opened.textContent = copy('opened'); row.append(opened);
|
||||
$('document-breadcrumb').textContent = result.name || copy('document');
|
||||
|
||||
@@ -45,4 +45,4 @@
|
||||
|
||||
---
|
||||
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.13.0 · Frontend v0.2.1
|
||||
Акылдаш · Telegram-бот v0.2.2 · Backend v0.13.0 · Frontend v0.2.2
|
||||
@@ -304,7 +304,7 @@ Figma-макет с Auto Layout и переменными, ссылающийс
|
||||
|
||||
## 11. Этап 8 — проверки, версия и PR
|
||||
|
||||
Этот этап относится к реализации в feature-ветке. Frontend повышен до `0.2.1`,
|
||||
Этот этап относится к реализации в feature-ветке. Frontend повышен до `0.2.2`,
|
||||
backend — до `0.13.0` для allowlist-выдачи локальных шрифтов. Контракт Search
|
||||
API v1 не менялся.
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 700 KiB After Width: | Height: | Size: 701 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
@@ -7,7 +7,7 @@ const html = fs.readFileSync(path.join(repo, 'backend/search/review.html'), 'utf
|
||||
const root = path.join(repo, 'backend/search/static/fonts');
|
||||
fs.mkdirSync(artifacts, {recursive: true});
|
||||
const docs = [
|
||||
{code:'doc-1',name:'Налоговый кодекс Кыргызской Республики',type:'Кодекс',status:'Действует',snippet:'… положения о налоге на прибыль и порядке его исчисления …',edition:'2024',editions:[{source_code:'2024',name:{ru:'Редакция от 12 января 2024 года'},available_languages:['ru','ky']} ]},
|
||||
{code:'doc-1',name:'Налоговый кодекс Кыргызской Республики',type:'Кодекс',status:'Действует',snippet:'… положения о налоге на прибыль и порядке его исчисления …',edition:'2024',editions:[{source_code:'2024',name:{ru:'Редакция от 12 января 2024 года'},available_languages:['ru','ky']},{source_code:'2023',name:{ru:'Редакция только на русском'},available_languages:['ru']} ]},
|
||||
{code:'doc-2',name:'О введении в действие Налогового кодекса Кыргызской Республики',type:'Закон',status:'Действует',snippet:'… устанавливается порядок введения в действие Налогового кодекса Кыргызской Республики …'},
|
||||
{code:'doc-3',name:'Порядок заполнения налоговой отчётности',type:'Постановление',snippet:'… рекомендации по заполнению налоговой отчётности, включая показатели по налогу на прибыль …'}
|
||||
];
|
||||
@@ -26,7 +26,9 @@ let failNextEdition = false; let delayNextEdition = false;
|
||||
if(p==='/search/filters') return route.fulfill({json:catalogs});
|
||||
if(p==='/search') return route.fulfill(url.searchParams.get('q')==='service-error' ? {status:503,json:{error:'Fixture search error'}} : {json:{results:url.searchParams.get('q')==='not-found'?[]:docs,has_next:false}});
|
||||
if(p==='/documents/doc-2/editions') return route.fulfill({json:{available_languages:[],editions:[]}});
|
||||
if(p==='/documents/doc-1/editions') return route.fulfill({json:{available_languages:['ru','ky'],editions:[{source_code:'2024',name:{ru:'Редакция от 12 января 2024 года'},available_languages:['ru','ky']}]}});
|
||||
if(p==='/documents/doc-3/editions') return route.fulfill({status:503,json:{error:'Fixture editions error'}});
|
||||
if(p==='/documents/doc-1/editions') return route.fulfill({json:{available_languages:['ru','ky'],editions:[{source_code:'2024',name:{ru:'Редакция от 12 января 2024 года'},available_languages:['ru','ky']},{source_code:'2023',name:{ru:'Редакция только на русском'},available_languages:['ru']}]}});
|
||||
if(p==='/documents/doc-1/editions/2023') return route.fulfill({json:{content:{ru:{html:'<h2>Russian-only edition</h2><p>Regression fixture.</p>'}}}});
|
||||
if(p==='/documents/doc-1/editions/2024') { if(delayNextEdition){delayNextEdition=false;await new Promise(resolve=>setTimeout(resolve,200));} if(failNextEdition){failNextEdition=false;return route.fulfill({status:503,json:{error:'Fixture edition error'}});} return route.fulfill({json:{content:{ru:{html:article},ky:{html:article}}}}); }
|
||||
if(p==='/static/favicon.svg') return route.fulfill({status:200,contentType:'image/svg+xml',body:fs.readFileSync(path.join(root,'../favicon.svg'))});
|
||||
if(p==='/static/favicon-32.png') return route.fulfill({status:200,contentType:'image/png',body:fs.readFileSync(path.join(root,'../favicon-32.png'))});
|
||||
@@ -44,7 +46,7 @@ let failNextEdition = false; let delayNextEdition = false;
|
||||
await page.addStyleTag({content:'.search__fixture-label{display:inline-block;margin-inline-start:0;padding-inline-start:12px;border-inline-start:1px solid #ded5cd;color:#786469;font-size:13px}' });
|
||||
await page.evaluate(()=>{const n=document.createElement('span');n.className='search__fixture-label';n.textContent='Демонстрационные данные';document.querySelector('#active-filters').after(n);const note=document.querySelector('#document-note');note.textContent='Текст приведён для примера оформления';note.hidden=false});
|
||||
console.log('subnavBoxes',await page.evaluate(()=>[...document.querySelector('.search__subnav').children].map(el=>({className:el.className||el.id,text:el.innerText,rect:(()=>{const r=el.getBoundingClientRect();return {left:r.left,right:r.right,width:r.width}})()})))); const navGeometry=await page.evaluate(()=>{const box=s=>{const r=document.querySelector(s).getBoundingClientRect();return {left:r.left,right:r.right}};const label=document.querySelector('.search__fixture-label'),range=document.createRange();range.selectNodeContents(label);const text=range.getBoundingClientRect();return {search:box('#search-nav'),sources:box('#sources-open'),language:box('.search__language-chip'),status:box('#active-filters .search__chip'),demoText:{left:text.left,right:text.right}}}); console.log('navGeometry',navGeometry); const resultHighlight=await page.locator('.search__result--selected .search__result-snippet mark').first().textContent(); const openedCount=await page.locator('.search__opened').count(); await page.locator('#public-document-panel').evaluate(el=>el.scrollTop=0); await page.evaluate(()=>document.activeElement.blur()); console.log('desktopBoxes', await page.evaluate(()=>Object.fromEntries(['.search__chrome','.search__layout','.search__footer','.search__document'].map(s=>[s,(()=>{const r=document.querySelector(s).getBoundingClientRect();return {top:r.top,bottom:r.bottom,height:r.height}})()])))); console.log('docElements',await page.evaluate(()=>Object.fromEntries(['.search__document-head','#public-document-heading','#public-document-meta','.search__document-toolbar','.search__find','#document-note','#public-document-body h2'].map(s=>[s,(()=>{const r=document.querySelector(s).getBoundingClientRect();return {top:r.top,bottom:r.bottom,height:r.height}})()])))); await page.screenshot({path:path.join(artifacts,'desktop-1280x800.png'),fullPage:false});
|
||||
const chipsBefore=await page.locator('#active-filters .search__chip').count(); await page.locator('input[name="filter-document_type"][value="code"]').check(); await page.waitForFunction(()=>new URL(location.href).searchParams.get('document_type')==='code'); const chipsWithTwo=await page.locator('#active-filters .search__chip').count(); await page.locator('#active-filters button').first().click(); await page.waitForFunction(()=>new URL(location.href).searchParams.get('status')===null); const preservedFilters=await page.evaluate(()=>({status:new URL(location.href).searchParams.get('status'),documentType:new URL(location.href).searchParams.get('document_type')})); await page.locator('#active-filters button').first().click(); const urlFilters=await page.evaluate(()=>({status:new URL(location.href).searchParams.get('status'),documentType:new URL(location.href).searchParams.get('document_type')})); const firstFindCount=await page.locator('#document-find-count').textContent(); await page.locator('#document-find-next').click(); const nextFindCount=await page.locator('#document-find-count').textContent(); await page.locator('input[name="filter-status"][value="active"]').check(); await page.locator('.search__result-title').first().click(); await page.locator('#document-find').fill('налог на прибыль'); await page.locator('#document-find-next').click(); await page.locator('#ui-ky').click(); const documentLanguage=await page.locator('#document-language').inputValue(); const kyrgyzUi=await page.evaluate(()=>({htmlLang:document.documentElement.lang,queryLabel:document.querySelector('label[for=public-query]').textContent,filterHeading:document.querySelectorAll('#filter-fields h3')[1].textContent,statusHeading:document.querySelectorAll('#filter-fields h3')[0].textContent,authority:document.querySelector('#filter-authority option').textContent,activeStatus:document.querySelector('#active-filters .search__chip').textContent.trim(),queryPlaceholder:document.querySelector('#public-query').placeholder,resultCount:document.querySelector('#result-count').textContent,findCount:document.querySelector('#document-find-count').textContent,opened:document.querySelector('.search__opened').textContent})); await page.locator('#ui-ru').click(); await page.locator('#document-expand').click(); const expanded=await page.locator('#document-dialog').evaluate(e=>e.open); await page.keyboard.press('Escape'); const expandedEscapeClosed=await page.locator('#document-dialog').evaluate(d=>!d.open); const expandedFocusRestored=await page.evaluate(()=>document.activeElement.id); await page.locator('#search-account-open').click(); const accountActions=await page.locator('#account-dialog').evaluate(d=>({open:d.open,reviewVisible:!d.querySelector('#review-nav').hidden,usersVisible:!d.querySelector('#search-users-open').hidden,logoutVisible:!d.querySelector('#search-logout').hidden})); await page.locator('#account-dialog').evaluate(d=>d.close()); await page.locator('#public-query').fill('not-found'); await page.locator('#public-search-form button[type=submit]').click(); await page.waitForFunction(()=>document.querySelector('#result-count').textContent.startsWith('0 ')); await page.locator('#ui-ky').click(); const kyrgyzEmpty=await page.evaluate(()=>({count:document.querySelector('#result-count').textContent,heading:document.querySelector('#public-results .review__empty strong').textContent})); await page.locator('#ui-ru').click(); await page.goto('https://fixture.invalid/?q=%D0%BD%D0%B0%D0%BB%D0%BE%D0%B3+%D0%BD%D0%B0+%D0%BF%D1%80%D0%B8%D0%B1%D1%8B%D0%BB%D1%8C&status=active'); await page.locator('.search__result-title').first().waitFor(); await page.locator('.search__result-title').first().click(); await page.locator('#public-document-body h2').first().waitFor(); await page.evaluate(()=>document.fonts.ready); await page.locator('#document-find').fill('налог на прибыль'); await page.addStyleTag({content:'.search__fixture-label{display:inline-block;margin-inline-start:12px;padding:5px 10px;border:1px solid #A37B32;border-radius:6px;color:#786469;font-size:14px}'}); await page.evaluate(()=>{const n=document.createElement('span');n.className='search__fixture-label';n.textContent='Демонстрационные данные';document.querySelector('.search__subnav').append(n);const note=document.querySelector('#document-note');note.textContent='Текст приведён для примера оформления';note.hidden=false;document.querySelector('#public-document-panel').scrollTop=0});
|
||||
const chipsBefore=await page.locator('#active-filters .search__chip').count(); await page.locator('input[name="filter-document_type"][value="code"]').check(); await page.waitForFunction(()=>new URL(location.href).searchParams.get('document_type')==='code'); const chipsWithTwo=await page.locator('#active-filters .search__chip').count(); await page.locator('#active-filters button').first().click(); await page.waitForFunction(()=>new URL(location.href).searchParams.get('status')===null); const preservedFilters=await page.evaluate(()=>({status:new URL(location.href).searchParams.get('status'),documentType:new URL(location.href).searchParams.get('document_type')})); await page.locator('#active-filters button').first().click(); const urlFilters=await page.evaluate(()=>({status:new URL(location.href).searchParams.get('status'),documentType:new URL(location.href).searchParams.get('document_type')})); const firstFindCount=await page.locator('#document-find-count').textContent(); await page.locator('#document-find-next').click(); const nextFindCount=await page.locator('#document-find-count').textContent(); await page.locator('input[name="filter-status"][value="active"]').check(); await page.locator('.search__result-title').first().click(); await page.locator('#document-find').fill('налог на прибыль'); await page.locator('#document-find-next').click(); await page.locator('#ui-ky').click(); const documentLanguage=await page.locator('#document-language').inputValue(); const kyrgyzUi=await page.evaluate(()=>({htmlLang:document.documentElement.lang,queryLabel:document.querySelector('label[for=public-query]').textContent,filterHeading:document.querySelectorAll('#filter-fields h3')[1].textContent,statusHeading:document.querySelectorAll('#filter-fields h3')[0].textContent,authority:document.querySelector('#filter-authority option').textContent,activeStatus:document.querySelector('#active-filters .search__chip').textContent.trim(),queryPlaceholder:document.querySelector('#public-query').placeholder,resultCount:document.querySelector('#result-count').textContent,findCount:document.querySelector('#document-find-count').textContent,opened:document.querySelector('.search__opened').textContent,statusValues:[...document.querySelectorAll('input[name="filter-status"]')].map(input=>input.parentElement.textContent.trim()),documentTypes:[...document.querySelectorAll('input[name="filter-document_type"]')].map(input=>input.parentElement.textContent.trim())})); await page.locator('#ui-ru').click(); await page.locator('#document-expand').click(); const expanded=await page.locator('#document-dialog').evaluate(e=>e.open); await page.keyboard.press('Escape'); const expandedEscapeClosed=await page.locator('#document-dialog').evaluate(d=>!d.open); const expandedFocusRestored=await page.evaluate(()=>document.activeElement.id); await page.locator('#search-account-open').click(); const accountActions=await page.locator('#account-dialog').evaluate(d=>({open:d.open,reviewVisible:!d.querySelector('#review-nav').hidden,usersVisible:!d.querySelector('#search-users-open').hidden,logoutVisible:!d.querySelector('#search-logout').hidden})); await page.locator('#account-dialog').evaluate(d=>d.close()); await page.locator('#public-query').fill('not-found'); await page.locator('#public-search-form button[type=submit]').click(); await page.waitForFunction(()=>document.querySelector('#result-count').textContent.startsWith('0 ')); await page.locator('#ui-ky').click(); const kyrgyzEmpty=await page.evaluate(()=>({count:document.querySelector('#result-count').textContent,heading:document.querySelector('#public-results .review__empty strong').textContent})); await page.locator('#ui-ru').click(); await page.goto('https://fixture.invalid/?q=%D0%BD%D0%B0%D0%BB%D0%BE%D0%B3+%D0%BD%D0%B0+%D0%BF%D1%80%D0%B8%D0%B1%D1%8B%D0%BB%D1%8C&status=active'); await page.locator('.search__result-title').first().waitFor(); await page.locator('.search__result-title').first().click(); await page.locator('#public-document-body h2').first().waitFor(); await page.evaluate(()=>document.fonts.ready); await page.locator('#document-find').fill('налог на прибыль'); await page.addStyleTag({content:'.search__fixture-label{display:inline-block;margin-inline-start:12px;padding:5px 10px;border:1px solid #A37B32;border-radius:6px;color:#786469;font-size:14px}'}); await page.evaluate(()=>{const n=document.createElement('span');n.className='search__fixture-label';n.textContent='Демонстрационные данные';document.querySelector('.search__subnav').append(n);const note=document.querySelector('#document-note');note.textContent='Текст приведён для примера оформления';note.hidden=false;document.querySelector('#public-document-panel').scrollTop=0});
|
||||
const fonts = await page.evaluate(async()=>({sans:document.fonts.check('400 16px "Noto Sans"'),serif:document.fonts.check('600 24px "Noto Serif"'),kyrgyzSans:(await document.fonts.load('400 16px "Noto Sans"','ӨөҮүҢң')).length>0,kyrgyzSerif:(await document.fonts.load('600 24px "Noto Serif"','ӨөҮүҢң')).length>0,ids:[...document.querySelectorAll('[id]')].map(x=>x.id).filter((id,i,a)=>a.indexOf(id)!==i)}));
|
||||
const widths={}; for(const width of [1440,1024]){await page.setViewportSize({width,height:900});widths[width]=await page.evaluate(()=>({scrollWidth:document.documentElement.scrollWidth,viewport:innerWidth,layout:document.querySelector('#search-layout').getBoundingClientRect().width}));await page.screenshot({path:path.join(artifacts,`desktop-${width}px.png`)});} await page.setViewportSize({width:320,height:800}); widths[320]=await page.evaluate(()=>({scrollWidth:document.documentElement.scrollWidth,viewport:innerWidth}));
|
||||
await page.setViewportSize({width:390,height:844});
|
||||
@@ -61,6 +63,10 @@ let failNextEdition = false; let delayNextEdition = false;
|
||||
await page.goto('https://fixture.invalid/review'); await page.locator('#review').waitFor(); const routes={searchHidden:await page.locator('#search-page').isHidden(),reviewVisible:await page.locator('#review').isVisible()};
|
||||
const tutorialScreens={}; for(const [name,viewport] of [['desktop',{width:1280,height:800}],['mobile',{width:390,height:844}]]){await page.setViewportSize(viewport); await page.locator('#tutorial-open').click(); tutorialScreens[name]=[]; for(const target of ['#query','#results','#document-panel','#save']){const geometry=await page.evaluate((selector)=>{const dialog=document.querySelector('#tutorial-dialog'),target=document.querySelector(selector),d=dialog.getBoundingClientRect(),t=target.getBoundingClientRect(),style=getComputedStyle(dialog),targetStyle=getComputedStyle(target);return {step:document.querySelector('#tutorial-progress').textContent,width:d.width,height:d.height,overlaps:d.left<t.right&&d.right>t.left&&d.top<t.bottom&&d.bottom>t.top,font:style.fontFamily,titleFont:getComputedStyle(document.querySelector('#tutorial-heading')).fontFamily,background:style.backgroundColor,targetDisplay:targetStyle.display,targetInViewport:t.bottom>0&&t.top<innerHeight,target:{left:t.left,top:t.top,right:t.right,bottom:t.bottom}}},target);if(geometry.targetDisplay==='none'||geometry.target.right===geometry.target.left||geometry.target.bottom===geometry.target.top||!geometry.targetInViewport) throw new Error(`Tutorial target is not visible at ${name}: ${JSON.stringify(geometry)}`);if(geometry.overlaps) throw new Error(`Tutorial overlaps ${target} at ${name}`);if(geometry.width>320||!geometry.font.includes('Noto Sans')||!geometry.titleFont.includes('Noto Serif')||!geometry.background.includes('0.92')) throw new Error(`Tutorial style mismatch at ${name}: ${JSON.stringify(geometry)}`);tutorialScreens[name].push(geometry);if(target==='#query') await page.screenshot({path:path.join(artifacts,`review-tour-${name}.png`),fullPage:false});if(target!=='#save') await page.locator('#tutorial-next').press('Enter');}await page.locator('#tutorial-next').press('Enter');}
|
||||
await page.setViewportSize({width:1280,height:800}); await page.goto('https://fixture.invalid/?q=%D0%BD%D0%B0%D0%BB%D0%BE%D0%B3%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B8%D0%B1%D1%8B%D0%BB%D1%8C&status=active');
|
||||
await page.locator('.search__result-title').first().click(); await page.locator('#public-document-body h2').first().waitFor(); await page.locator('#document-language').selectOption('ky'); await page.locator('#document-edition').selectOption('2023'); await page.waitForFunction(()=>document.querySelector('#public-document-body h2')?.textContent==='Russian-only edition'); const languageFallback={language:await page.locator('#document-language').inputValue(),heading:await page.locator('#public-document-body h2').textContent()};
|
||||
await page.locator('.search__result-title').nth(1).click(); await page.waitForFunction(()=>document.querySelector('#document-facts').textContent==='Для документа нет доступных редакций с текстом.'); await page.locator('#document-expand').click(); const noEditionExpandedText=await page.locator('#dialog-body').textContent(); const noEditionExpandedHeading=await page.locator('#dialog-heading').textContent(); await page.keyboard.press('Escape');
|
||||
await page.locator('.search__result-title').first().click(); await page.locator('#public-document-body h2').first().waitFor(); await page.locator('.search__result-title').nth(2).click(); await page.waitForFunction(()=>document.querySelector('#document-facts').textContent==='Fixture editions error'); await page.locator('#document-expand').click(); const editionErrorExpandedText=await page.locator('#dialog-body').textContent(); const editionErrorExpandedHeading=await page.locator('#dialog-heading').textContent(); await page.keyboard.press('Escape');
|
||||
await page.goto('https://fixture.invalid/?q=%D0%BD%D0%B0%D0%BB%D0%BE%D0%B3%20%D0%BD%D0%B0%20%D0%BF%D1%80%D0%B8%D0%B1%D1%8B%D0%BB%D1%8C&status=active');
|
||||
await page.locator('.search__result-title').first().click(); await page.locator('#public-document-body h2').first().waitFor();
|
||||
await page.locator('#sources-open').click(); const sourcesDialog=await page.evaluate(()=>({open:document.querySelector('#sources-dialog').open,labelledBy:document.querySelector('#sources-dialog').getAttribute('aria-labelledby'),focusInside:document.querySelector('#sources-dialog').contains(document.activeElement)})); await page.keyboard.press('Escape'); const sourcesEscapeClosed=await page.locator('#sources-dialog').evaluate(d=>!d.open); const sourcesFocusRestored=await page.evaluate(()=>document.activeElement.id);
|
||||
await page.locator('#search-account-open').click(); const accountDialogFocusInside=await page.locator('#account-dialog').evaluate(d=>d.contains(document.activeElement)); await page.keyboard.press('Escape'); const accountEscapeClosed=await page.locator('#account-dialog').evaluate(d=>!d.open); const accountFocusRestored=await page.evaluate(()=>document.activeElement.id);
|
||||
@@ -74,7 +80,7 @@ let failNextEdition = false; let delayNextEdition = false;
|
||||
await page.setViewportSize({width:1280,height:800});
|
||||
await page.locator('#search-account-open').click(); const accountControls=page.locator('#account-dialog a, #account-dialog button, #account-dialog input'); await accountControls.last().focus(); await page.keyboard.press('Tab'); const accountTabTarget=await page.evaluate(()=>({tag:document.activeElement.tagName,id:document.activeElement.id,dialog:document.querySelector('#account-dialog').contains(document.activeElement)})); const accountTabWrap=accountTabTarget.dialog; await page.keyboard.press('Shift+Tab'); const accountReverseTabWrap=await page.evaluate(()=>document.activeElement.getAttribute('data-close')); await page.keyboard.press('Escape');
|
||||
const longContent=await page.evaluate(()=>{const result=document.querySelector('.search__result-title'),heading=document.querySelector('#public-document-heading'),oldResult=result.textContent,oldHeading=heading.textContent;const longName='Кыргызская Республика '.repeat(8);result.textContent=longName;heading.textContent=longName;const image=document.createElement('img');image.width=2200;image.height=100;image.src='data:image/svg+xml,'+encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="2200" height="100"><rect width="2200" height="100" fill="gold"/></svg>');document.querySelector('#public-document-body').append(image);const titleWidths=[result,heading].map(el=>({client:el.clientWidth,scroll:el.scrollWidth}));const imageWidth=image.getBoundingClientRect().width,bodyWidth=document.querySelector('#public-document-body').clientWidth,pageWidth=document.documentElement.scrollWidth;result.textContent=oldResult;heading.textContent=oldHeading;image.remove();return {titleWidths,imageWidth,bodyWidth,pageWidth,viewport:innerWidth}});
|
||||
console.log('subnavAcceptance',JSON.stringify({sourcesDialog,sourcesEscapeClosed,sourcesFocusRestored,accountDialogFocusInside,accountEscapeClosed,accountFocusRestored,printState,tableOverflow,reducedMotion,searchError,editionError,editionRetry,noEditions,expandedEscapeClosed,expandedFocusRestored,editionLoading,accountTabWrap,accountTabTarget,accountReverseTabWrap,longContent,tutorialScreens}));
|
||||
console.log('subnavAcceptance',JSON.stringify({sourcesDialog,sourcesEscapeClosed,sourcesFocusRestored,accountDialogFocusInside,accountEscapeClosed,accountFocusRestored,printState,tableOverflow,reducedMotion,searchError,editionError,editionRetry,noEditions,languageFallback,noEditionExpandedText,noEditionExpandedHeading,editionErrorExpandedText,editionErrorExpandedHeading,expandedEscapeClosed,expandedFocusRestored,editionLoading,accountTabWrap,accountTabTarget,accountReverseTabWrap,longContent,tutorialScreens}));
|
||||
console.log(JSON.stringify({resultHighlight,fonts,favicons,widths,view,drawer,modalBackground,focusWrap,focusVisible,focusReverseWrap,focusRestored,back,chipsBefore,chipsWithTwo,preservedFilters,urlFilters,openedCount,firstFindCount,nextFindCount,documentLanguage,kyrgyzUi,kyrgyzEmpty,expanded,accountActions,desktopFilters,routes,errors},null,2));
|
||||
const referencePath=process.env.REFERENCE_IMAGE || '/home/vaka/.codex/attachments/662a46bf-0f7f-4bba-a63a-bb823f4470dc/image-1.png';
|
||||
if(fs.existsSync(referencePath)) {
|
||||
@@ -100,5 +106,5 @@ let failNextEdition = false; let delayNextEdition = false;
|
||||
await comparePage.close();
|
||||
}
|
||||
await browser.close();
|
||||
if(resultHighlight!=='налоге на прибыль' || navGeometry.search.left!==26 || navGeometry.search.right!==123 || navGeometry.sources.left!==146 || Math.abs(navGeometry.sources.right-244)>3 || Math.abs(navGeometry.language.left-284)>2 || Math.abs(navGeometry.language.right-372)>2 || navGeometry.status.left!==381 || Math.abs(navGeometry.demoText.left-510)>3 || errors.length || !fonts.sans || !fonts.serif || !fonts.kyrgyzSans || !fonts.kyrgyzSerif || fonts.ids.length || favicons.length!==2 || favicons.some(item=>item.status!==200) || Object.values(widths).some(x=>x.scrollWidth>x.viewport) || view.scrollWidth>view.width || drawer!=='true' || Object.values(modalBackground).some(value=>value!==true) || focusWrap!=='clear-filters' || focusVisible!=='3px' || focusReverseWrap==='clear-filters' || focusRestored!=='mobile-filters-open' || back!=='results' || openedCount!==1 || chipsBefore!==1 || chipsWithTwo!==2 || preservedFilters.status!==null || preservedFilters.documentType!=='code' || urlFilters.status!==null || urlFilters.documentType!==null || firstFindCount!=='1 из 3' || nextFindCount!=='2 из 3' || documentLanguage!=='ru' || kyrgyzUi.htmlLang!=='ky' || kyrgyzUi.queryLabel!=='Укуктук база боюнча издөө' || kyrgyzUi.statusHeading!=='Статус' || kyrgyzUi.filterHeading!=='Документтин түрү' || kyrgyzUi.authority!=='Бардык органдар' || kyrgyzUi.activeStatus!=='Күчүндө' || kyrgyzUi.resultCount!=='3 натыйжа бетте' || kyrgyzUi.findCount!=='2 ичинен 3' || kyrgyzUi.opened!=='Ачылды' || kyrgyzEmpty.count!=='0 натыйжа бетте' || kyrgyzEmpty.heading!=='Документтер табылган жок' || !expanded || !expandedEscapeClosed || expandedFocusRestored!=='document-expand' || !accountActions.open || !accountActions.reviewVisible || !accountActions.usersVisible || !accountActions.logoutVisible || desktopFilters.visible!==true || desktopFilters.ariaHidden!==null || routes.searchHidden!==true || routes.reviewVisible!==true || !sourcesDialog.open || sourcesDialog.labelledBy!=='sources-heading' || !sourcesDialog.focusInside || !sourcesEscapeClosed || sourcesFocusRestored!=='sources-open' || !accountDialogFocusInside || !accountEscapeClosed || accountFocusRestored!=='search-account-open' || printState.chrome!=='none' || printState.filters!=='none' || printState.results!=='none' || printState.footer!=='none' || printState.document==='none' || printState.source==='none' || printState.sourceHidden || tableOverflow.scrollWidth<=tableOverflow.clientWidth || tableOverflow.pageWidth>tableOverflow.viewport || reducedMotion.scrollBehavior!=='auto' || reducedMotion.transitionDuration!=='0s' || searchError.message!=='Fixture search error' || searchError.query!=='service-error' || searchError.role!=='status' || editionError!=='Fixture edition error' || editionRetry!=='Налог на прибыль' || noEditions.message!=='Для документа нет доступных редакций с текстом.' || noEditions.bodyChildren!==0 || !editionLoading || !accountTabWrap || accountTabTarget.id!=='review-nav' || accountReverseTabWrap!=='account-dialog' || longContent.titleWidths.some(item=>item.scroll>item.client) || longContent.imageWidth>longContent.bodyWidth || longContent.pageWidth>longContent.viewport) process.exit(1);
|
||||
if(resultHighlight!=='налоге на прибыль' || navGeometry.search.left!==26 || navGeometry.search.right!==123 || navGeometry.sources.left!==146 || Math.abs(navGeometry.sources.right-244)>3 || Math.abs(navGeometry.language.left-284)>2 || Math.abs(navGeometry.language.right-372)>2 || navGeometry.status.left!==381 || Math.abs(navGeometry.demoText.left-510)>3 || errors.length || !fonts.sans || !fonts.serif || !fonts.kyrgyzSans || !fonts.kyrgyzSerif || fonts.ids.length || favicons.length!==2 || favicons.some(item=>item.status!==200) || Object.values(widths).some(x=>x.scrollWidth>x.viewport) || view.scrollWidth>view.width || drawer!=='true' || Object.values(modalBackground).some(value=>value!==true) || focusWrap!=='clear-filters' || focusVisible!=='3px' || focusReverseWrap==='clear-filters' || focusRestored!=='mobile-filters-open' || back!=='results' || openedCount!==1 || chipsBefore!==1 || chipsWithTwo!==2 || preservedFilters.status!==null || preservedFilters.documentType!=='code' || urlFilters.status!==null || urlFilters.documentType!==null || firstFindCount!=='1 из 3' || nextFindCount!=='2 из 3' || documentLanguage!=='ru' || kyrgyzUi.htmlLang!=='ky' || kyrgyzUi.queryLabel!=='Укуктук база боюнча издөө' || kyrgyzUi.statusHeading!=='Статус' || kyrgyzUi.filterHeading!=='Документтин түрү' || kyrgyzUi.authority!=='Бардык органдар' || kyrgyzUi.activeStatus!=='Күчүндө' || JSON.stringify(kyrgyzUi.statusValues)!==JSON.stringify(['Күчүндө (24)','Күчүн жоготту (5)']) || JSON.stringify(kyrgyzUi.documentTypes)!==JSON.stringify(['Кодекс (12)','Мыйзам (10)','Токтом (8)']) || kyrgyzUi.resultCount!=='3 натыйжа бетте' || kyrgyzUi.findCount!=='2 ичинен 3' || kyrgyzUi.opened!=='Ачылды' || kyrgyzEmpty.count!=='0 натыйжа бетте' || kyrgyzEmpty.heading!=='Документтер табылган жок' || !expanded || !expandedEscapeClosed || expandedFocusRestored!=='document-expand' || !accountActions.open || !accountActions.reviewVisible || !accountActions.usersVisible || !accountActions.logoutVisible || desktopFilters.visible!==true || desktopFilters.ariaHidden!==null || routes.searchHidden!==true || routes.reviewVisible!==true || !sourcesDialog.open || sourcesDialog.labelledBy!=='sources-heading' || !sourcesDialog.focusInside || !sourcesEscapeClosed || sourcesFocusRestored!=='sources-open' || !accountDialogFocusInside || !accountEscapeClosed || accountFocusRestored!=='search-account-open' || printState.chrome!=='none' || printState.filters!=='none' || printState.results!=='none' || printState.footer!=='none' || printState.document==='none' || printState.source==='none' || printState.sourceHidden || tableOverflow.scrollWidth<=tableOverflow.clientWidth || tableOverflow.pageWidth>tableOverflow.viewport || reducedMotion.scrollBehavior!=='auto' || reducedMotion.transitionDuration!=='0s' || searchError.message!=='Fixture search error' || searchError.query!=='service-error' || searchError.role!=='status' || editionError!=='Fixture edition error' || editionRetry!=='Налог на прибыль' || noEditions.message!=='Для документа нет доступных редакций с текстом.' || noEditions.bodyChildren!==0 || languageFallback.language!=='ru' || languageFallback.heading!=='Russian-only edition' || noEditionExpandedText!=='' || !noEditionExpandedHeading.includes('О введении в действие') || editionErrorExpandedText!=='' || !editionErrorExpandedHeading.includes('Порядок заполнения') || !editionLoading || !accountTabWrap || accountTabTarget.id!=='review-nav' || accountReverseTabWrap!=='account-dialog' || longContent.titleWidths.some(item=>item.scroll>item.client) || longContent.imageWidth>longContent.bodyWidth || longContent.pageWidth>longContent.viewport) process.exit(1);
|
||||
})();
|
||||