PlayerHUD

Bloco de gamificação modular para o Moodle — documentação completa.

English | Português

View on GitHub Download .zip

🧪 Testes Automatizados

O PlayerHUD inclui uma suíte de testes extensa que cobre tanto a lógica de negócio (PHPUnit) quanto a aceitação em navegador (Behat). Todo push de CI executa a matriz completa (Moodle 4.5 → 5.x, PostgreSQL e MariaDB).

PHPUnit — Testes Unitários e de Integração

Arquivo de teste Casos O que é coberto
ai/chat_test.php 2 Log de auditoria do chat do assistente: a mensagem do professor registrada é cortada em 255 caracteres, nunca no meio de um caractere multibyte (log_chat() acessado por reflexão, sem chamar a IA)
ai/generator_test.php 19 save_item() (via reflection, sem rede): limita um nome gerado por IA acima do tamanho; converte campos não-string antes de persistir; sanitiza uma descrição maliciosa; is_safe_url() rejeita um host que não resolve e um literal de IP não-canônico (guarda de SSRF)
ai/hub_usage_reporting_test.php 2 Reporta toda tentativa de IA que falhou quando uma chave do hub é usada, não só a última; nunca reporta uso de uma chave própria do plugin (fora do hub)
backup_restore_item_images_test.php 1 Imagens de item: o arquivo de imagem de um item é restaurado exatamente uma vez, com o novo id do item
backup_restore_test.php 3 Definições de backup/restore cobrem todas as tabelas RPG; round-trip completo de curso (incl. duas atividades reais) preserva dados de classe/capítulo/história RPG, poderes de item (action_type/action_value), tiers de emoji da classe, e o requisito de uma quest TYPE_SPECIFIC_TRADE remapeado contra a troca restaurada em vez do mapeamento de item; um cmid fixado no deadline_extension e o requisito de uma quest TYPE_ACTIVITY são ambos remapeados para a atividade do curso restaurado; um segundo item deadline_extension com seu próprio cmid confirma que o lookup em lote de after_restore() não mistura o cmid de um item com o de outro
block_dashboard_test.php 3 Tratamento do Painel: o bloco não é um formato aplicável no Painel, mas uma instância deixada lá por uma versão anterior continua podendo ser excluída (user_can_addto()); páginas de curso mantêm a regra padrão
collection_tab_test.php 17 Aba Coleção: mapeamento de filter_type (avatar/prazo/nenhum), power_hint_avatar exibido para item não-secreto não possuído e oculto para secreto, flag is_equipped; classificação de origem para a fonte de uma linha de inventário (map é reconhecida como própria do PlayerHUD; qualquer coisa fora das 4 fontes conhecidas cai para uma origem genérica “game”); get_lp_activities() é memoizado entre múltiplos itens na mesma renderização (N+1 corrigido); has_power() é falso pra action_type não reconhecido e pra deadline_extension sem o plugin companheiro instalado; uma contagem de origem grande renderiza compacta; um item possuído só pelo motor novo de quantidade (sem linhas legadas de inventário) ainda conta e ganha o badge “novo”; um saldo dividido entre as duas gerações de armazenamento soma corretamente num único card; uma quantidade alta renderiza pelo utils::format_compact_number(); get_lp_activities() exclui uma atividade oculta mesmo com regra de penalidade por atraso habilitada, enquanto uma visível com a mesma regra continua aparecendo; um item de prazo que fixa um único cmid oculto resolve pra uma lista de atividades vazia em vez de vazar o nome dela
db_access_test.php 2 O riskbitmask declarado de block/playerhud:manage inclui RISK_PERSONAL, já que o painel de gerenciamento expõe XP, inventário e atribuição de classe de outros usuários
db_upgrade_test.php 9 Passos de upgrade com lógica real de migração de dados (diferente de DDL de schema puro): marca um item pré-existente literalmente chamado ‘PlayerCoin’ com action_type=playercoin, deixa intocados itens não relacionados/já marcados; retroalimenta xpawarded numa linha de inventário pré-existente de uma origem que paga XP de verdade (map/teacher/revoked) usando o XP atual do item, mas não para uma linha de origem drop nem uma linha coletada de um drop infinito (maxusage=0); retroalimenta o xpawarded de uma reivindicação de quest pré-existente a partir do reward_xp atual da quest; retroalimenta um código de drop faltante com um código único que não colide com um já existente na mesma instância; o passo de upgrade adiciona as tabelas block_playerhud_stack/block_playerhud_stack_log e as colunas de quantidade de drop/quest sem tocar em nenhuma linha existente; adiciona a coluna timerevoked em block_playerhud_stack_log sem tocar em nenhuma linha existente
drop_guard_test.php 11 Limites de coleta, itens consumidos por troca, aplicação de cooldown; um limite de coleta é aplicado a partir de um saldo mantido só no log do motor novo de quantidade; um limite combinando linhas legadas de inventário e linhas do log novo bloqueia corretamente; um saldo combinado das duas fontes abaixo do limite ainda é permitido; um cooldown é aplicado a partir da coleta mais recente do motor novo, não só de uma linha legada de inventário
edit_form_test.php 5 Validação do formulário de configuração do bloco: “XP por nível” abaixo de um é rejeitado, um valor positivo é aceito
form/edit_item_form_test.php 8 Validação server-side do campo de imagem do item: valor emoji passa; URL HTTPS válida passa; payload de quebra de atributo (http seguido de aspas/tags) é rejeitado; valor http-like malformado que não é uma URL de verdade é rejeitado
form/edit_scene_form_test.php 3 Rótulos de “próxima cena” no formulário de cena: cortados por caracteres (UTF-8 válido), entidades HTML decodificadas, reticências só quando houve corte
game_test.php 48 get_game_stats() totaliza XP/nível mais a inclusão de XP de quests (e exclusão quando a quest está desabilitada), verificado contra o próprio total de analytics::economy_health(); anti-farm de coleta e cooldown; get_avatar_item (habilitado, desabilitado, instância estrangeira, não encontrado); XP concedido ao coletar drop com uso finito; exclusão de gerentes do ranking; flags de milestone de level-up, vitória no jogo e primeira PlayerCoin na coleta; xp_to_level; criação automática de jogador, alternância de gamificação e visibilidade no ranking, inventário (exclui revogados/consumidos), has_item; get_user_rank ordem por XP, desempate por chegada, exclusão de gerentes e de não matriculados; hidratação de requisitos/recompensas em get_full_trades, caso vazio, e bloqueio de disponibilidade quando o item de qualquer um dos lados está desabilitado; heurística de sugestões de troca (avatares com desconto, pulo de avatar já coberto, pré-requisitos) e persistência, com o emoji do avatar escapado (strip_tags) em build_trade_suggestions; change_xp emite o evento xp_changed ao conceder, ao deduzir (piso em zero) e fica em silêncio num no-op de verdade; get_leaderboard sinaliza corretamente o grupo do próprio usuário no ranking por grupo e restringe o filtro de grupo do professor aos membros reais; process_collection() concede várias unidades de uma vez através do campo value do drop; maxusage e value se multiplicam de forma independente (um drop com 3 usos e 2 por uso rende 6 no total ao longo de 3 coletas, bloqueado na 4ª tentativa independente do value); a resposta da coleta reporta a quantidade concedida e o texto de progresso; get_leaderboard() pode pular o nome e a data de exibição, que add_display_fields() acrescenta depois só às entradas mostradas
gamemaster_test.php 6 Conceder, revogar e excluir item e quest pelos controllers reais: o XP é aplicado, a data de desempate do ranking só avança em ganho, XP mínimo em zero
instance_delete_test.php 2 Excluir uma instância do bloco limpa todas as tabelas próprias do plugin (instance_cleanup), incl. as novas tabelas block_playerhud_stack/block_playerhud_stack_log; uma guarda lê todas as tabelas do install.xml e exige que o fixture as cubra e que a limpeza as esvazie, para que uma tabela nova não seja esquecida
item_delete_cascade_test.php 25 Detecção de trocas órfãs ao excluir item (único req, um de dois, único reward, combinado req+reward); verificações em lote; isolamento cross-instance; exclusão remove o item e cascateia trocas órfãs sem afetar as não-órfãs; excluir um item (único ou em lote) reverte XP só das cópias que realmente ganharam XP, deixando intactas as cópias de drop infinito (XP zero); a exclusão única e em lote de um item também remove suas linhas de block_playerhud_stack/block_playerhud_stack_log e reverte o XP que elas registraram; excluir um item depois que uma de suas unidades do stack já foi revogada (isolada ou por uma revogação legada em lote) reverte só o XP ainda pendente, sem reverter em dobro a parte já recuperada; find_xp_impact ignora um detentor cujo saldo inteiro do stack já foi revogado
karma_test.php 11 Leitura/escrita de karma, deltas positivos/negativos, clamping nos limites ±999, acumulação sucessiva
lib_test.php 27 block_playerhud_myprofile_navigation: todo ramo de no-op (sem curso, curso do site, sem instância do bloco, sem registro de jogador, gamificação desabilitada) e um jogador ativo com item coletado recebe a seção de perfil, incl. um viewer estudante comum (não admin); a seção só é montada quando o viewer tem block/playerhud:view e moodle/block:view no contexto do bloco (capability negada de qualquer um dos dois é no-op); um estudante oculto do leaderboard (ranking_visibility = 0) mantém o perfil visível, pois só pausar a gamificação o remove, e o próprio dono e um professor com block/playerhud:manage também o veem; block_playerhud_get_drop_details_by_code: correspondência, código desconhecido, rejeição de instância estrangeira, exclusão de item desabilitado; block_playerhud_is_visible_for_class: público (vazio/’0’), classe correspondente/não correspondente, ‘0’ dentro de uma lista; block_playerhud_pluginfile: contexto não-bloco, área de arquivo desconhecida, nenhum arquivo armazenado encontrado, e a mesma checagem de block/playerhud:view/moodle/block:view da seção de perfil (qualquer um dos dois negado rejeita o download, um estudante comum com ambos passa) antes de servir a arte de item/classe
privacy_provider_test.php 26 LGPD com cobertura completa: descoberta de contexto/usuário (get_contexts_for_userid, get_users_in_context); export_user_data nas seis subárvores (perfil, RPG, inventário, missões, trocas, logs de IA), incl. xp_gained de missão e created/modified do progresso de RPG, que antes ficavam de fora; exclusão por usuário, multiusuário e de contexto inteiro com garantia de isolamento, incl. a limpeza de wizard_objects/wizard_shortcodes antes de wizard_runs (evitando linhas órfãs com runid inexistente); exportação/exclusão de toda chave de API e preferência de avatar; declaração de metadados completa por coluna real da tabela (não por chave individual) para playerhud_user, inventory, ai_logs, wizard_runs, rpg_progress, quest_log e trade_log; guardas de contexto não-bloco como no-ops; a declaração de metadados também é checada contra toda coluna real das novas tabelas block_playerhud_stack e block_playerhud_stack_log; get_contexts_for_userid() ainda encontra um usuário cujo único dado mora numa única tabela, e sua contagem de queries de descoberta se mantém estável não importa quantas tabelas do plugin realmente têm dado desse usuário (guarda N+1); uma requisição de contexto alcança a checagem da tabela de concessão dentro de add_contextlist_for_user() mesmo pra um usuário sem nenhuma linha em playerhud_user
quest_test.php 53 Verificações de conclusão (nível, XP, itens, trocas, conclusão de atividade); reivindicar recompensas; quest desabilitada; idempotência; flags de comemoração de level-up e vitória no jogo ao reivindicar recompensa; has_claimable_quests em todos os tipos de requisito incl. conclusão de atividade, com curto-circuito de reivindicadas/não reivindicadas; preload_totals() bate com os resultados não-agrupados do próprio check_status() por quest em todo tipo apoiado em query, incl. duas quests TYPE_SPECIFIC_ITEM/TYPE_SPECIFIC_TRADE com ids diferentes, e sua contagem de leituras se mantém estável conforme a lista de quests cresce em vez de escalar; has_claimable_quests() mantém suas próprias contagens por tipo isoladas entre itens/trocas e nunca paga por um agregado de um tipo que uma quest anterior na lista já curto-circuitou antes de alcançar; mapeamento de build_record_from_suggestion, transporte de item-ids e piso do override de XP; get_heuristic_suggestions milestones de nível/coleção/economia/atividade com pulo de duplicatas; uma atividade com acompanhamento de conclusão oferecida como missão heurística é detectada como cumprida assim que a atividade é realmente concluída; TYPE_SPECIFIC_ITEM/TYPE_UNIQUE_ITEMS/TYPE_TOTAL_ITEMS são satisfeitos por um saldo mantido só no motor novo de quantidade, não só em linhas legadas de inventário; reivindicar uma recompensa concede o reward_itemqty configurado da quest, não sempre exatamente uma unidade; a mensagem de recompensa mantém o espaçamento do conector intacto
rpg_classes_test.php 3 Consulta de classe (get_player_class) e limites de tier de retrato
story_manager_test.php 34 Carregamento de cena, persistência de progresso, navegação de escolhas, delta de karma, conclusão de capítulo, casos de erro; a consequência set_class_id de uma escolha atribui a classe RPG do jogador — o único caminho de atribuição — e dispara character_selected; make_choice() adquire e libera o lock por usuário sem travar numa chamada normal; inventário consumed/revoked é rejeitado como pagamento de uma escolha com custo, uma cópia genuína é aceita e consumida (e soft-consumida — vira source=consumed, não é excluída, então a linha sobrevive); load_scene(), make_choice() e load_recap() revalidam unlock_date/required_level do capítulo no servidor (antes só a UI bloqueava o clique), com load_recap() isento dessa checagem para um capítulo já concluído — reler algo legitimamente terminado nunca re-trava, mesmo que o professor aperte as regras depois; gastar um item numa escolha de história nunca devolve uma coleta do drop finito/com cooldown de onde o item veio, já que o drop_guard continua contando a linha (agora consumida, não excluída) contra o maxusage do drop; um custo de escolha é aceito quando o pagamento vem só do saldo do motor novo de quantidade; um custo de escolha é aceito quando o pagamento é dividido entre as duas gerações de armazenamento
suggest_trades_state_test.php 4 Botão Sugerir Trocas: desabilitado sem pré-requisitos, desabilitado só com moeda, desabilitado quando todos os avatares cobertos, habilitado com cobertura parcial
template_strings_test.php 3 Os templates tiram seus textos visíveis e anunciados de strings de idioma: rótulos das medalhas do ranking, rótulo do emoji do gerador de shortcode e a unidade de respawn do modal de IA (verificado com um en_local personalizado)
trade_test.php 12 Montagem de trocas, fundos insuficientes, sucesso atômico, limite único, restrição por grupo; uma troca que referencia um item de recompensa desabilitado é rejeitada de imediato mesmo com saldo suficiente; uma troca de quantidade alta concede/consome pelo motor de quantidade sem inserir uma linha de inventário por unidade; o custo de uma troca é pago corretamente quando dividido entre as duas gerações de armazenamento
uninstall_test.php 2 O hook de pré-desinstalação (xmldb_block_playerhud_uninstall()) exclui só as linhas de user_preferences com prefixo block_playerhud_, deixando intocada uma preferência de outro plugin (mesmo uma que compartilha o trecho playerhud, ex.: filter_playerhud_pref) e uma preferência não relacionada; rodar o hook sem nada pra excluir é um no-op inofensivo
utils_test.php 23 get_avatar_html: emoji gera div ph-avatar-emoji com span aria-hidden; URL HTTP gera tag img ph-avatar-img; imagem nula não lança exceção em get_avatar_html nem em get_items_display_data; generate_drop_code() retorna um código de 6 caracteres alfanuméricos maiúsculos, e gerar/persistir vários códigos seguidos para a mesma instância nunca produz um repetido; sanitize_rich_description() remove um esqueleto de modal colado, preserva formatação básica, remove uma tag <script> nua, e neutraliza uma URI javascript:; format_compact_number() deixa um valor abaixo de 1000 inalterado, usa sufixo k pra milhares, sufixo M pra milhões, e preserva o sinal negativo; format_drop_progress() renderiza tanto a contagem quanto o badge pra um limite de coleta finito e um ilimitado; formatação da quantidade por coleta do drop; get_block_config() devolve uma configuração vazia para configdata NULL, vazio ou inválido, e a configuração real nos demais casos
Subtotal 364  

Testes de Lógica de Negócio Compartilhada (tests/local/)

Lógica reutilizada por mais de um ponto de entrada (as próprias web services do assistente, a tela manual de “Distribuir Drops”, o painel Economy Health), testada diretamente em vez de só indiretamente através de quem quer que a chame.

Arquivo de teste Casos O que é coberto
analytics_test.php 13 Economy Health: razão entre XP total ganhável e o teto (vazio/difícil/perfeito/fácil), recompensas de quest e itens infinitos/sem drop no detalhamento, guarda de teto zero; histograma de distribuição de níveis, ordenação do overflow do cap (N+), percentual da barra mais alta, guarda de XP-por-nível zero, conjunto de jogadores vazio não produz linhas; o XP atual de balance_context() sempre bate com o total do próprio economy_health(); o teto de XP alcançável considera o value de um drop, não só sua contagem de itens
audit_log_test.php 16 Consulta compartilhada de log de auditoria (get_logs()) usada pela aba Relatórios do professor e pela aba Histórico do estudante: o xp_gained de um item reflete o valor xpawarded registrado no momento da concessão, não o XP atual do item (e bate quando nunca editado); um item concedido por quest reporta xp_gained zero, já que seu próprio XP nunca é pago por esse caminho; uma linha revogada reporta o negativo do valor originalmente registrado, não o XP atual do item; a reivindicação de uma quest reflete o valor registrado, não o reward_xp atual da quest; uma concessão/consumo/revogação do motor novo aparece cada uma como seu próprio tipo de evento, com a revogação reportando um valor negativo; o details de um consumo ganha o prefixo consumed_ pra resolver pra sua própria string report_src_consumed_* em vez de colidir com a tag do lado da concessão de mesmo nome, enquanto um consumo sem tag de origem mantém o details genérico consumed sem prefixo; uma concessão legada reporta quantidade um e um consumo legado reporta quantidade menos um; uma concessão/consumo/revogação do motor novo reportam sua quantidade real (positiva/negativa) em vez de sempre um; eventos de troca e quest sempre reportam quantidade zero
diagnostics/integrity_test.php 14 Integridade (diagnóstico): os mapas de tabelas de órfãs e de registros soltos cobrem todas as tabelas do install.xml; uma instância cuja linha em block_instances sumiu (ou de id 0) é apontada, com jogadores, XP e itens, e com o curso recuperado do registro real de xp_changed no log, marcado como existente ou apagado; órfãs cujo curso ainda existe vêm primeiro e a lista tem limite; registros soltos são contados por tabela; a limpeza apaga só a órfã selecionada e todos os registros soltos (inclusive uma escolha que fica solta quando a cena dela sai), deixa a instância viva intacta, ignora um id vivo enviado pelo formulário, não grava cópia quando não há nada a apagar, e salva uma cópia JSON que pode ser listada, encontrada só pelo nome exato e apagada
diagnostics/report_test.php 3 Relatório de diagnóstico: todas as seções que a página lê estão presentes; um relatório recente é reaproveitado até ser atualizado ou invalidado; um relatório com mais de dez minutos é recalculado
diagnostics/usage_test.php 2 Números de diagnóstico: a adoção por curso se divide em três grupos que somam o total, conta os recursos ativados e separa um bloco no Painel; o engajamento conta jogadores, desistências e XP nas instâncias vivas, e a atividade só dentro da janela de 90 dias, deixando de fora os registros de instâncias removidas
drop_distribution_test.php 13 Descoberta de módulos elegíveis: inclui fóruns, exclui módulos em exclusão e o fórum de avisos do curso (reservado para PlayerCoin/Item Secreto), exclui labels quando solicitado, vazio para curso sem atividades; sugestão por melhor correspondência de nome incl. caso sem correspondência; busca de cmid por shortcode já inserido incl. não encontrado e entrada vazia; divisão de cotas por atividade sempre soma o alvo, limita ao número de atividades, casos de borda
external_items_test.php 27 API de itens entre plugins usada por outros plugins da família Player (ex.: PlayerWords): belongs_to_instance() aceita a própria instância de um item (habilitado ou desabilitado) e rejeita instância estrangeira, id inexistente, ou ids zero/negativos sem consultar o banco; grant() atualiza o saldo do motor novo de quantidade e concede XP para o próprio item habilitado de quem chama, e registra o dropid que disparou a concessão na linha do log; consume() gasta primeiro do saldo novo, cai para linhas legadas de inventário quando o saldo novo está vazio, gasta corretamente quando o saldo está dividido entre as duas gerações, e retorna false quando insuficiente nas duas; get_available_quantity() soma as duas gerações de armazenamento; get_available_quantities_bulk() bate com o que consultar cada item individualmente retornaria, e sua contagem de leituras se mantém estável conforme a lista de itens cresce em vez de escalar; get_name()/get_xp() resolvem para a própria instância do item e retornam vazio/zero para uma estrangeira
latepenalty_bridge_test.php 2 Ponte com o local_latepenalty: o prazo de uma atividade é informado como prazo do estudante; sem prazo devolve null, não um timestamp zero
wizard_test.php 22 Manifesto da rodada: status de início/fim; desfazer exclui objetos registrados em todas as tabelas, remove o shortcode registrado, reverte XP e limpa o histórico de jogo, rejeita instância incompatível; desfazer remove vários shortcodes registrados de atividades diferentes reconstruindo o cache do curso uma única vez, não uma vez por shortcode; desfazer exclui trocas e capítulos registrados (com seus trade_reqs e story_nodes) pelos mesmos caminhos em lote bulk_delete_trades()/bulk_delete_chapters() que os controladores de trocas/capítulos já têm, exercitado de ponta a ponta via wizard::rollback() em vez de chamar os métodos do controlador direto; listagem de rodadas ativas com contagens e limite; detecção de “já gerado” por mecânica incl. rodadas obsoletas sem conteúdo, itens só no manifesto, itens só logados pela IA e a checagem só-de-config do Ranking; ensure_config_flag liga uma flag sem tocar em config irmã e não faz nada quando já está ligada; require_course_matches_instance() aceita o curso real da instância do bloco e rejeita um courseid de qualquer outro curso
xp_budget_test.php 15 Contagens de item/missão/capítulo por tamanho de jornada incl. fallback pra curta; distribute_share divide a folga igualmente, espalha o resto nos primeiros elementos, limita à folga quando há mais elementos que ela, casos de borda; mapeamento de níveis-máximos sugeridos; rodízio balanceado de missões entre tipos, preservação de ordem dentro de um tipo, todas selecionadas quando o limite as cobre, casos de borda
Subtotal 127  

Testes de Web Services (tests/external/)

Uma classe de teste por função de web service, validando o contrato da API externa, conformidade de parâmetros e estrutura de retorno (external_api::clean_returnvalue), e guardas de capability. As funções de IA são testadas sem rede — sem chave de API configurada, o bloco try/catch retorna success=false, que é assegurado diretamente.

Arquivo de teste Casos O que é coberto
chat_message_test.php 4 Sem chave de API → moodle_exception, assegurando o errorcode específico accessdenied em vez de qualquer moodle_exception onde relevante (uma chave de API não configurada também lançaria uma e mascararia uma checagem de vínculo ausente); campo message com HTML sujo é rejeitado; guarda de capability (manage); um courseid incompatível é rejeitado antes de qualquer chamada de IA ser tentada
collect_item_test.php 5 Item coletado + registro de inventário criado; drop inválido → success=false; limite atingido → success=false; guarda de capability (view); um courseid incompatível é rejeitado
create_avatar_pack_test.php 7 17 itens criados; ids e nomes retornados em lockstep; todos com action_type=avatar; deduplicação por emoji; segunda chamada cria 0 (idempotência); guarda de capability; um courseid incompatível é rejeitado
create_class_pack_test.php 8 Cria 3 classes; tiers de HP base conforme esperado; pula nome já existente; segunda chamada cria 0 (idempotência); tons diferentes geram nomes diferentes; tom desconhecido cai no fallback fantasia; guarda de capability; um courseid incompatível é rejeitado
create_playercoin_test.php 4 Novo item criado; segunda chamada retorna existente (idempotência); guarda de capability; um courseid incompatível é rejeitado
execute_chat_action_test.php 6 action_open_tab retorna URL de redirect (determinístico, sem IA); tipo de ação desconhecido → success=false; parâmetros inválidos → success=false; guarda de capability; um courseid incompatível é rejeitado
generate_ai_content_test.php 4 Sem chave de API → success=false; campo message com HTML sujo é rejeitado; guarda de capability (manage); um courseid incompatível é rejeitado
generate_class_oracle_test.php 4 Sem chave de API → success=false; campo message com HTML sujo é rejeitado; guarda de capability (manage); um courseid incompatível é rejeitado
generate_story_test.php 4 Sem chave de API → success=false; campo message com HTML sujo é rejeitado; guarda de capability (manage); um courseid incompatível é rejeitado
insert_drop_shortcode_test.php 12 Shortcode inserido no campo de conteúdo do módulo; inserção duplicada rejeitada; drop de outra instância rejeitado; drop renomeado pra atividade em que caiu; mode=text com rótulo customizado; modo desconhecido cai pra card; guarda de capability; execute_batch() insere shortcodes para vários drops numa só chamada (usada pela distribuição do assistente), e uma falha isolada num drop não bloqueia o processamento dos demais; dois drops caindo na mesma atividade e campo no mesmo lote sobrevivem ambos (o cache de valores de campo pré-carregado é atualizado in-place a cada escrita, não lido uma vez só pro lote inteiro); a contagem de leituras do lote se mantém estável conforme cresce em vez de escalar com o número de itens
load_recap_test.php 3 HTML de recap gerado após visita à cena; sem histórico → exceção; guarda de capability (view)
load_scene_test.php 4 Nó inicial e escolhas retornados; capítulo inválido → exceção; guarda de capability (view)
make_choice_test.php 3 Avança a história até o nó de destino; escolha inválida → exceção; guarda de capability (view)
remove_drop_shortcode_test.php 8 Shortcode existente removido; shortcode separado por <br> removido; shortcode com atributos mode=/text= removido; ausência de shortcode é noop sem erro; guarda de capability; execute_batch() remove o shortcode de vários drops numa só chamada reconstruindo o cache do curso uma única vez; a variante em lote tem sua própria guarda de capability
setup_playercoin_drop_test.php 7 Sucesso; sem fórum → success=false; item de outra instância rejeitado; curso que não é dono da instância rejeitado; shortcode anteposto ao intro existente; guarda de capability
use_item_test.php 13 Guarda de capability (view, assegurada via uma capability dedicada interact); item não possuído → exceção; poder de prazo: sem atividade, sem regra, cria override e consome item, atualiza override existente, rejeita um targetcmid de outro curso mesmo quando já existe um override pra esse cmid+userid, rejeita um cmid estrangeiro antes mesmo de consultar a tabela de regra do plugin companheiro, e rejeita um targetcmid que o usuário atual não pode ver (atividade oculta) com a mesma mensagem exata de “sem regra configurada”, rodado como estudante em vez de admin pra realmente exercitar a checagem de visibilidade; duas concessões sequenciais legítimas (dois itens em posse) consomem cada uma sua própria unidade e estendem exatamente um bloco de dias por chamada, sem dobrar a extensão de nenhuma das duas; poder de avatar: equipar e desequipar funcionam mesmo com $OUTPUT resetado pro placeholder bootstrap_renderer ainda não resolvido, reproduzindo a mesma condição de uma requisição AJAX real, e equipar continua funcionando pra um avatar concedido só pelo motor novo de quantidade
wizard_apply_suggested_levels_test.php 3 Aplica a sugestão quando a config está nos padrões; ainda aplica quando a config já foi customizada; preserva todo outro campo de config intocado
wizard_generate_helpers_test.php 13 build_step_types() bate com os módulos selecionados na ordem, pula auto_distribute quando o distribuir de Itens está desligado, vazio quando nada selecionado; compute_shared_xp_shares() vazio sem Itens/Missões, Pill/Extensão de Prazo usam seus próprios padrões sozinhos, dividem o orçamento com Itens quando combinados; resolve_or_create_progress_item() idempotente e cria um item completo quando falta; resolve_previous_chapter_context() lê o capítulo mais recente; distribute_drops() limita cada atividade à sua cota calculada em vez de deixar só a correspondência de nome empilhar todo drop numa única atividade, espalha entre todas as atividades elegíveis pulando labels; um id de drop de outra instância é silenciosamente excluído em vez de distribuído, sem vazar seu nome nem inflar a contagem de cota; uma lista de drop ids vazia retorna cedo em vez de chegar no get_in_or_equal() sem nada pra casar
wizard_list_runs_test.php 4 Resumo de uma rodada ativa; rodada de RPG resumida; rodadas desfeitas excluídas; guarda de capability
wizard_rollback_test.php 4 Exclui os objetos gerados pela rodada, contagem reportada bate com o que foi registrado; rejeita instância incompatível; guarda de capability
wizard_run_step_test.php 57 Um passo de progresso ao vivo por vez, por mecânica (PlayerCoin, Avatares, Missões, Comércio, Colecionável de Conhecimento, Item Secreto, Ranking, Extensão de Prazo, RPG, Item RPG, auto-distribuir): criação de item/quest/troca com registro no manifesto, retentativas idempotentes, desfazer por mecânica, controle pela flag de distribuir, tom/tamanho de jornada influenciando o conteúdo, e a inserção exclusiva no fórum de avisos pra PlayerCoin e Item Secreto (incl. no-op sem fórum de avisos); tipo de passo desconhecido, guarda de capability, rejeição de runid de outra instância, passo com falha não finaliza a rodada, passo final reporta a economia só quando solicitado; um courseid que não é o dono de fato da instância do bloco é rejeitado
wizard_start_test.php 9 Um passo de plano por módulo selecionado; a flag de “passo lento” reflete se Próximo Capítulo foi selecionado; a divisão de cotas de XP bate com os módulos selecionados; o XP bônus da Pill presente quando selecionada sozinha; o módulo de arco da história se expande num outline + um passo por capítulo, a quantidade de passos cresce com o tamanho da jornada, o manifesto mantém o nome lógico do módulo; guarda de capability; um courseid que não é o dono de fato da instância do bloco é rejeitado
Subtotal 186  

Testes de Controlador (tests/controller/)

Cobrem a lógica de negócio extraída do manage.php para os controladores (refatoração MVC), cada um exercitado com entradas explícitas e isolamento de instância.

Arquivo de teste Casos O que é coberto
aikeys_test.php 4 Armazenamento de chaves de IA: chaves aparadas e salvas como preferências do usuário, padrão vazio para campo ausente, chaves legadas removidas do config do bloco, config limpo intocado
chapters_test.php 17 Persistência e ordenação de capítulos: salvar (inserir, atualizar, padrões, isolamento), excluir em cascata cenas/escolhas, mover/reordenar com renumeração da lista completa, no-op na borda; bulk_delete_chapters() remove vários capítulos junto com suas cenas e escolhas numa só passada, filtra um capítulo de outra instância em vez de excluí-lo ou lançar exceção, e é no-op para lista de ids vazia; mover um capítulo não escala o número de escritas com o total de capítulos
classes_test.php 7 Persistência de classe RPG: inserção (HP base, vínculo de instância, emojis por tier), atualização preserva HP base, trim de emoji, isolamento; exclusão remove registro e retratos por tier, isolamento, irmãos preservados
drops_test.php 19 Persistência de drop: salvar (inserir + código, ilimitado, atualizar preserva propriedade, isolamento, item estrangeiro); excluir único e no-op estrangeiro; exclusão em massa só dos próprios com contagem, entrada vazia; get_owned_item retorna para a instância dona e rejeita instância estrangeira; get_sort_data alterna ícone/direção da coluna de ordenação ativa; view_manage_page/handle_edit_form renderizam de ponta a ponta pelo $OUTPUT/$PAGE globais reais para um professor com a capability de gerenciamento; o template da tabela de gerenciamento escapa o conteúdo de mídia do drop; view_manage_page ordena corretamente por nome e cai numa coluna segura diante de um parâmetro de ordenação desconhecido em vez de interpolá-lo no SQL; a tela do assistente de distribuir drops escapa o atributo de dados dos cmids inseridos, e o template da sidebar escapa a URL de gerenciamento
export_test.php 10 Construtor da exportação de notas: campos da linha e nível derivado, ordenação por XP, teto de nível, exclusão de professores/gerentes, colunas localizadas sem jogadores, exclusão de não matriculados, desempate por última ação; o email de um estudante é omitido da exportação quando oculto pela política de identidade do site
items_test.php 26 Ciclo de vida do item: toggle de ativação e no-op estrangeiro; conceder adiciona inventário + XP, 0 XP, rejeição estrangeira, e rejeita um usuário que não está de fato matriculado no curso da instância; revogar desconta XP, preserva drop infinito, no-op estrangeiro, é idempotente numa segunda revogação (XP descontado só uma vez), e é no-op numa cópia já consumida; revogar desconta o XP realmente registrado no momento da concessão, não o XP atual do item; detecção de trocas sobreviventes (troca aparada, órfã excluída, não relacionada ignorada); find_xp_impact soma só as cópias que realmente ganharam XP entre todos os detentores, vazio para item nunca possuído, e no-op para lista de ids vazia; conceder com um $qty explícito concede várias unidades pelo motor de quantidade numa só chamada; revogar uma entrada de block_playerhud_stack_log desconta o saldo e o XP registrado, limita a remoção ao saldo atual, é no-op numa linha de consume, é idempotente numa segunda revogação da mesma entrada (mantendo intactas as unidades de outras concessões do mesmo usuário), deixa a concessão de origem sem sinalizar quando seu saldo já foi todo gasto, e é no-op numa entrada de instância estrangeira
manage_entry_points_test.php 34 A metade HTTP dos controladores, exercitada pelo ciclo real de requisição (superglobais preenchidas como um navegador faria, redirect() capturado como redirecterrordetected sob CLI): as ações de excluir e excluir em lote de drops realmente removem as linhas, um id de drop de outra instância nunca é excluído, uma sesskey errada não exclui nada, a listagem renderiza só os drops da própria instância e cai num campo de ordenação seguro diante de um parâmetro sort forjado; excluir uma cena cascateia para suas escolhas, um nó de outro capítulo fica intacto, um capítulo de outra instância é rejeitado; coletar concede o item e seu XP, não paga XP em drop infinito, rejeita drop de outra instância, item desabilitado e sesskey inválida sem gravar nada; os editores de classe e troca rejeitam um registro de outra instância, e todas essas telas ficam fechadas para quem não tem block/playerhud:manage (ou :view no coletar); uma requisição genuinamente submetida (com o marcador _qf__ do moodleform, como um POST real carrega) para uma troca de outra instância ainda é rejeitada antes de seus requisitos/recompensas serem lidos, fechando um oráculo que vazaria as contagens de linha de uma troca alheia no dimensionamento do formulário re-renderizado; um GET simples com repeats_req/repeats_give acima do normal é limitado em vez de alocar um número ilimitado de elementos de formulário, e scenes::clamp_repeats() limita um valor de repeats fornecido pelo cliente ao mesmo teto fixo; a página de drops, o coletar, e os editores de capítulos/classe/troca/cenas rejeitam cada um um courseid que não é o dono de fato da instância do bloco; os editores de troca e drop escapam um nome de item malicioso embutido no select de itens e no cabeçalho da página
quests_test.php 14 Ciclo de vida da missão: toggle e no-op estrangeiro; excluir reverte XP por conclusão, sem recompensa, no-op estrangeiro; excluir e excluir em lote revertem o XP realmente registrado por conclusão, não a recompensa atual da quest; massa só dos próprios com reversão agregada de XP e contagem, entrada vazia; find_xp_impact soma só as conclusões que realmente ganharam XP entre todos os reivindicantes, vazio para quest nunca reivindicada, e no-op para lista de ids vazia; a prévia de impacto de XP de uma exclusão em lote exclui uma quest de outra instância assim que a lista de ids é escopada à instância
scenes_test.php 6 Persistência de cena/escolha da história: salvar escolhas, atribuição de classe com normalização de ID string/int (regressão set_class_id), classe requerida, próximo nó, custo de item, criação de nó de continuação
suggestions_test.php 4 Persistência de sugestões: só as missões marcadas são inseridas (e nenhuma selecionada), só as trocas marcadas são criadas com reqs/recompensas (e nenhuma selecionada)
trades_test.php 10 Persistência de troca: salvar (inserir com reqs + recompensas, atualizar substitui, isolamento, item estrangeiro filtrado); excluir em cascata reqs/recompensas/log, isolamento, irmãos preservados; bulk_delete_trades() remove várias trocas junto com seus requisitos, recompensas e log numa só passada, filtra uma troca de outra instância em vez de excluí-la ou lançar exceção, e é no-op para lista de ids vazia
Subtotal 151  

Testes de Saída / Renderer (tests/output/)

Arquivo de teste Casos O que é coberto
admin/diagnostics_test.php 6 Dados da página de diagnóstico: cada número vira um cartão, os que usam janela dizem isso e as solicitações à IA listam os provedores; o cartão do Painel só aparece quando há blocos lá; órfãs cujo curso ainda existe começam desmarcadas; o template real renderiza o formulário de limpeza, os registros soltos em palavras e a lista de cópias, ou a mensagem de ‘nada encontrado’; uma lista parcial de órfãs diz quantas estão sendo mostradas
chapters_escaping_test.php 8 Nomes escapados exatamente uma vez: títulos de capítulo nas listas do professor e do estudante, título e selos do editor de cenas, e nomes de classe e item que uma escolha da história envia ao script do player como texto puro
manage/item_delete_confirm_test.php 12 Contexto da confirmação de exclusão de item: ação única vs massa e payload de IDs, rótulos de confirmação singular/plural/simples, seções só-sobreviventes e órfãs+sobreviventes; aviso de impacto de XP exibido numa exclusão única com link de desabilitar-em-vez-de, nunca exibido numa exclusão em lote mesmo com URL de alternância fornecida, e omitido por completo quando não há impacto de XP
manage/item_disable_confirm_test.php 3 Contexto da confirmação de desativar item: lista as quests afetadas por papel e envia o toggle confirmado; um papel sem quests deixa seu bloco de fora; nomes são filtrados para texto puro
manage/quest_delete_confirm_test.php 3 Contexto de confirmação de exclusão de quest: exclusão única produz a ação delete_quest_force com o aviso de impacto de XP e o link de desabilitar-em-vez-de; exclusão em lote produz bulk_delete_quests_force com a lista de ids e nunca mostra o link de desabilitar mesmo com uma URL de alternância fornecida; sem impacto de XP omite tanto o aviso quanto o link
manage/tab_chapters_test.php 4 Avisos de visibilidade do card de capítulo: sinalização de cena inicial ausente, texto e limites do aviso de nível acima do máximo
manage/tab_config_test.php 4 Aba Config (resumo Economy Health): uma instância sem itens ou quests ainda exporta um resumo vazio bem formado, sem quebrar no economy_health(); um item com XP contribui pro detalhamento e pro total alcançável
manage/tab_items_escaping_test.php 7 Nomes escapados exatamente uma vez: a lista de itens (incluindo a confirmação de exclusão), a lista de todas as drops e a tela de distribuição, com formatstringstriptags ligado e desligado
manage/tab_items_test.php 3 Consultas de penalidade por atraso da aba de itens: só as atividades do próprio curso com regra ativa são oferecidas, e a consulta de regras é restrita aos ids das atividades do curso em vez de ler o site inteiro
manage/tab_quests_test.php 4 Processamento do formulário de quest: um esqueleto de modal colado na descrição é sanitizado antes de salvar; formatação básica na descrição é preservada; o select de item de recompensa escapa um nome de item malicioso
manage/tab_reports_test.php 22 Aba Relatórios: uma instância sem jogadores/itens/quests ainda exporta um resumo bem formado com o drill-down de auditoria inativo; display() renderiza HTML real de ponta a ponta pelo $OUTPUT global; mais de 30 logs de IA só exportam a primeira página (30) por padrão, mais recentes primeiro, e ai_showall=1 retorna todos; o fallback de inventory.source sem lang string report_src_* correspondente é repassado cru ao template como detail_text no drill-down de auditoria, e o tab_reports.mustache é comprovado que escapa via duplo-mustache na renderização; o tab_reports.mustache monta os badges de xp_gained/qty ele mesmo a partir de inteiros simples mais flags de positivo/negativo (os três estados: positivo, negativo, e o fallback neutro “-“), fechando a mesma brecha de escape por triple-mustache que a correção do detail_text já tinha fechado; o seletor de estudante da exportação omite o email de um estudante quando oculto pela política de identidade do site; o KPI “item mais coletado” e o total de itens de cada estudante somam as duas gerações de armazenamento; a tabela de estudantes é paginada em 50 com o contador seguindo o deslocamento, a ordenação vale entre páginas, uma página além do fim é ajustada à última, a busca por nome casa nas duas ordens do nome e escapa curingas de LIKE, a URL da paginação mantém busca e ordenação, a lista de seleção de estudante some acima de 200 jogadores (render real do template), e o total de itens fica certo numa página posterior tanto ordenando por XP (ids da página primeiro) quanto por itens
player_screens_escaping_test.php 18 Nomes escapados exatamente uma vez: coleção e perfil do estudante, aba de classes, página de drops e grupos do ranking; nomes que sugestões e o assistente gravam (sugestões de quest e de troca, uma drop renomeada com o nome da atividade) ficam como texto puro e os formulários de sugestão os escapam ao imprimir
profile_content_test.php 1 export_for_template() remove tags do campo de conteúdo da imagem do item
quests_escaping_test.php 5 Nomes escapados exatamente uma vez: nomes de quest, recompensa, item exigido, troca e capítulo na lista de quests do professor e na aba de quests do estudante
reports_escaping_test.php 6 Nomes escapados exatamente uma vez: a visão geral dos relatórios, o log de auditoria de um estudante (itens, custos de troca, quests) e o histórico do próprio estudante
trades_escaping_test.php 4 Nomes escapados exatamente uma vez: nomes de troca, requisito e recompensa, títulos de popover e a confirmação de exclusão na aba de trocas do professor e na loja do estudante
view/header_test.php 2 Cabeçalho do HUD: um jogador sem avatar equipado cai pra foto de perfil padrão, carregando nome/XP/nível sem badge de grupo (sem mod_playergroup); um item de avatar equipado substitui a foto de perfil padrão
view/tab_chapters_test.php 6 Aba Capítulos: sem capítulos renderiza o estado vazio em vez de quebrar; um capítulo desbloqueado e não concluído é listado como disponível; um capítulo registrado na lista completed_chapters do jogador renderiza como concluído; um capítulo com data de desbloqueio futura renderiza como bloqueado; um capítulo com required_level acima do nível do jogador também renderiza como bloqueado, coerente com a checagem que o servidor agora aplica
view/tab_history_test.php 4 Aba Log: um jogador sem eventos registrados ainda exporta um estado vazio bem formado, com os 5 cabeçalhos de coluna ordenável presentes; o fallback de inventory.source sem lang string report_src_* correspondente é repassado cru ao template como detail_text, e o tab_history.mustache é comprovado que escapa via duplo-mustache na renderização; o tab_history.mustache monta os badges de xp_gained/qty ele mesmo a partir de inteiros simples mais flags de positivo/negativo (os três estados: positivo, negativo, e o fallback neutro “-“), fechando a mesma brecha de escape por triple-mustache que a correção do detail_text já tinha fechado
view/tab_quests_test.php 6 Aba Quests: sem quests renderiza a notificação de vazio em vez de quebrar; uma quest concluída e não reivindicada é listada com ação de reivindicar; uma quest já reivindicada mostra a data de reivindicação em vez da ação; get_type_label() mapeia toda constante de tipo de quest pra um rótulo e cai no fallback pra um tipo não reconhecido
view/tab_ranking_test.php 13 Aba Ranking: desabilitado na config do bloco encerra antes de tocar em qualquer dado de jogador; um estudante visível vê o conteúdo do ranking; um estudante oculto vê seu próprio toggle de privacidade mas não o ranking; um professor sempre vê o conteúdo com os controles de filtro exclusivos de professor ativos; paginate() fatia 50 por página, ajusta páginas fora do intervalo e fixa a linha do próprio estudante (com a posição real) só quando ela está fora da página; de ponta a ponta, um estudante em 87º de 120 vê as posições 1–50 mais a própria linha fixada com o nome, a segunda página do professor não tem linha fixada e o link de ocultar/mostrar mantém página e filtro; o template real renderiza a linha fixada e a barra de páginas
view/tab_rules_test.php 2 Aba Regras/ajuda: uma config sem help_content cai no template padrão do sistema, carregando as flags de recurso habilitado dos cards de ajuda padrão; help_content customizado com use_default_help desabilitado renderiza o conteúdo próprio do professor em vez do padrão
view/tab_shop_test.php 4 Aba Loja: sem trocas renderiza o estado vazio em vez de quebrar; um estudante com o item exigido em quantidade suficiente pode pagar a troca; um estudante sem o item exigido não pode pagar; uma troca de uso único já concluída é marcada como tal
Subtotal 147  
Total geral 975  
vendor/bin/phpunit --testsuite block_playerhud

Cobertura de linhas por classe (PHPUnit + Xdebug):

Classe Cobertura de linhas
ai\chat 10%
ai\generator 65%
controller\aikeys 100%
controller\chapters 63%
controller\classes 67%
controller\collect 100%
controller\drops 91%
controller\export 92%
controller\items 95%
controller\quests 99%
controller\scenes 75%
controller\suggestions 100%
controller\trades 79%
drop_guard 100%
event\character_selected 43%
event\item_collected 43%
event\quest_collected 43%
event\trade_completed 43%
event\xp_changed 43%
external\chat_message 79%
external\collect_item 100%
external\create_avatar_pack 85%
external\create_class_pack 80%
external\create_playercoin 91%
external\execute_chat_action 54%
external\generate_ai_content 78%
external\generate_class_oracle 68%
external\generate_story 72%
external\insert_drop_shortcode 94%
external\load_recap 100%
external\load_scene 80%
external\make_choice 79%
external\remove_drop_shortcode 92%
external\setup_playercoin_drop 91%
external\use_item 82%
external\wizard_apply_suggested_levels 83%
external\wizard_generate 85%
external\wizard_list_runs 100%
external\wizard_rollback 100%
external\wizard_run_step 86%
external\wizard_start 100%
form\edit_item_form 75%
form\edit_scene_form 98%
form\suggest_quests_form 94%
form\suggest_trades_form 95%
game 95%
instance_cleanup 100%
local\analytics 92%
local\audit_log 80%
local\diagnostics\integrity 94%
local\diagnostics\report 100%
local\diagnostics\usage 100%
local\drop_distribution 74%
local\external_items 95%
local\latepenalty_bridge 50%
local\rpg_archetypes 92%
local\wizard 97%
local\xp_budget 98%
output\admin\diagnostics 100%
output\manage\item_delete_confirm 100%
output\manage\item_disable_confirm 100%
output\manage\quest_delete_confirm 100%
output\manage\tab_chapters 99%
output\manage\tab_classes 87%
output\manage\tab_config 82%
output\manage\tab_items 75%
output\manage\tab_quests 85%
output\manage\tab_reports 97%
output\manage\tab_trades 90%
output\profile_content 86%
output\view\header 95%
output\view\tab_chapters 100%
output\view\tab_collection 88%
output\view\tab_history 90%
output\view\tab_quests 91%
output\view\tab_ranking 83%
output\view\tab_rules 78%
output\view\tab_shop 92%
privacy\provider 97%
quest 96%
story_manager 79%
trade_manager 89%
utils 63%
Total 81%

83 das 91 classes do plugin aparecem acima — as demais (majoritariamente classes de exceção, observadores de evento e wrappers finos de output nunca carregados via require durante a execução desta suíte) não têm nenhum dado de cobertura e são omitidas em vez de aparecerem como um 0% enganoso.

Os percentuais mais baixos da tabela refletem limites estruturais, não lacunas de teste:

Arquivos de Função Solta

O db/upgrade.php e o lib.php só definem funções globais, não classes, então o detalhamento por classe do moodle-coverage acima não tem a quem atribuí-los. Os dois são instrumentados e entram no percentual do Total; medidos isoladamente:

Arquivo Casos Cobertura de linhas
lib.php 27 95%
db/upgrade.php 9 78%

Behat — Testes de Aceitação

Arquivo de feature Cenários O que é coberto
block_playerhud_access.feature 3 Visibilidade do bloco por perfil (professor adiciona bloco, aluno vê HUD, não matriculado não vê)
block_playerhud_student.feature 6 HUD ativo na primeira visita, desativar/reativar gamificação, dispensar confirmação; abrir a aba Log não dá erro; equipar um item de avatar pelo round-trip AJAX real não dá erro
block_playerhud_teacher.feature 7 Botão do Painel do Mestre, acesso ao painel de gerenciamento, navegação entre abas, retorno ao curso; abrir o log de auditoria de um aluno em Relatórios não dá erro
block_playerhud_modals.feature 5 Abrir/fechar modal de detalhes do item, proteção contra abertura duplicada, coleta AJAX sem redirecionamento, sem placeholders brutos
block_playerhud_celebrations.feature 2 Introdução do Huddy exibida uma única vez no painel; aviso de primeira quest exibido uma única vez quando há recompensa a reivindicar
block_playerhud_wizard.feature 6 Assistente abre mostrando o formulário de geração; abas laterais de Ajuda e Recomendações externas; gerar PlayerCoin de ponta a ponta mostra o relatório de sucesso; o card do PlayerCoin trava depois de gerado; desfazer uma rodada pelo Histórico destrava de novo
block_playerhud_manage_crud.feature 7 As telas de gestão que os testes em PHP só alcançam isoladamente: as abas Trocas, Personagens e História renderizam numa requisição real; a biblioteca de itens leva até a tela de drops; um drop é criado pelo moodleform real, aparece na listagem e mostra a notificação de sucesso (travando a regressão do tipo de notificação do redirect()); um personagem é criado pelo formulário real (incluindo os campos de gerenciador de arquivos); o checkbox mestre de seleção em lote marca e desmarca todas as linhas (JavaScript)
block_playerhud_manage_js.feature 9 O JavaScript das telas de personagens, configurações e missões: apagar um personagem depois de confirmar no modal, ou cancelar e mantê-lo; revelar e esconder uma chave de API; apagar uma missão depois de confirmar ou mantê-la ao recusar; o botão de exclusão em lote conta as missões selecionadas, volta a ficar desativado quando a seleção é limpa e apaga a seleção depois de confirmar
block_playerhud_story_js.feature 6 O JavaScript da história: o estudante lê um capítulo até a cena final e abre o resumo da história; o professor faz a prévia de um capítulo até o fim sem salvar progresso, inclusive um que termina numa escolha “Fim do capítulo” e exclui um capítulo e uma cena pelos modais de confirmação
block_playerhud_distribute_oracle_js.feature 5 Distribuição de drops: selecionar uma drop habilita o botão de inserir com a contagem, a inserção do shortcode numa atividade continua lá depois de recarregar, e desfazer com confirmação a devolve a pendente; o oráculo de personagens pede um tema antes de chamar a IA e, sem IA disponível, informa a falha e libera o botão
block_playerhud_items_drops_js.feature 11 Itens e drops: o modal de prévia do item, exclusão de itens em lote e individual com confirmação, as opções de drop do gerador de itens e seus erros de tema e de falta de IA dentro do modal, os botões de PlayerCoin e de pacote de avatares, o código do gerador de shortcode em cada modo de exibição, exclusão de drops em lote e individual, e a contagem regressiva que o estudante vê numa drop em recarga
block_playerhud_trades_reports_js.feature 5 Trocas e relatórios: excluir uma troca com confirmação, desmarcar e marcar de novo todas as sugestões de troca, a prévia do item escolhido como requisito no formulário de troca, abrir o log de um estudante pelo seletor e filtrá-lo pela busca ao vivo, e retirar um item de um estudante com confirmação
block_playerhud_pagination.feature 5 Com 61 jogadores ranqueados: um estudante abaixo da primeira página vê a própria linha fixada sob ela; a segunda página lista os jogadores seguintes e deixa de fixar o estudante que está nela; um professor que oculta um estudante continua na mesma página; a tabela de relatórios mostra a segunda página; a busca por nome encontra um estudante
block_playerhud_diagnostics.feature 3 O administrador encontra a página de uso e diagnóstico em Relatórios e vê os números; nada é apagado sem marcar a confirmação; confirmando, os dados deixados por um bloco removido são apagados e a cópia salva aparece na lista
Total 80  
php admin/tool/behat/cli/init.php
vendor/bin/behat --tags=@block_playerhud --profile=chrome